Exit Intent Popup Script and Tutorial
November 28th, 2014An exit intent popup is an HTML window that shows dynamically when a visitor goes to close your website. Visitor mouse movements are tracked, and when their cursor moves outside the upper page boundary, the popup is triggered. This popup can be anything, but will usually consist of a call to action (sign up to a newsletter, claim a discount, etc). Exit intent popups are used to hopefully retain visitors that would otherwise be lost.
After browsing the web for awhile, it became apparent there wasn't a freely available exit intent popup script that's built for today's browsers. The few I came across were using older technology that no longer works. The code and guide on how to use them was pretty unclear as well. I decided to build one for use on my own SaaS application, and I'm making the source code for it available here.
UPDATE Nov 21st, 2015: All future updates to this script will be available to download from the buttons below and in the GitHub repository. Continually modifying this tutorial as the code changes on GitHub is pretty time consuming, so the actual tutorial for the popup script below will remain as is from this date forward. To get the latest version of this script in full, use the Download buttons below, or visit the GitHub repository.
Features
- Fully customizable via HTML and CSS.
- Can use third party forms to collect emails.
- Support for embeddable CSS fonts, including Google Fonts.
- Cookie support with optional expiry date.
- Set a timed delay before the script starts tracking exit intent.
- Display popup based on exit intent or timed delay.
- Scales to adjust to window size.
Usage
The script is written in vanilla JavaScript, so no other libraries like jQuery are needed with it. Simply include it and initialize it from within the head element on your page.
<script type="text/javascript" src="bioep.min.js"></script>
<script type="text/javascript">
bioEp.init({
html: '',
css: ''
});
</script>
An exit popup will now be created with the default settings. With no HTML or CSS set in the init options, it will just be a blank box. Below is the default structure of the popup that's created and added to your page, which consists of a background, the popup itself, and a close button.
<div id="bio_ep_bg"></div>
<div id="bio_ep">
<div id="bio_ep_close">X</div>
<!-- Your HTML goes here -->
</div>
The close button is absolutely positioned, so it will not affect the layout of your content inside the popup. For the popup to actually show content, you must add in your own HTML and CSS.
Adding in HTML and CSS
There are two methods for adding HTML and CSS to the popup.
Method One
The first is a pure JavaScript solution, where you set the html and css option in the bioEp.init function call.
bioEp.init({
html: '<div id="content">Your HTML goes here</div>',
css: '#content {font-size: 20px;}'
});
Method Two
The second method allows you to add HTML and CSS on the page itself, without using JavaScript. You can simply call the init function without the html and css options, then add in your popup styles within the head element and your HTML within a tag as the last child of the body element.
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="bioep.min.js"></script>
<script type="text/javascript">
bioEp.init();
</script>
<style type="text/css">
// Popup CSS goes here
</style>
</head>
<body>
<div id="bio_ep">
// Popup HTML goes here
</div>
</body>
</html>
The script will automatically recognize the bio_ep element and use it as the main popup. Base styles to create the background and handle the popup are added, as well as your own custom styles. Using this method, any HTML added via method one will be ignored, but CSS will still be applied from both methods.
Templates
These templates are provided for you to use and to help illustrate how you would go about creating your own custom popup using this script. Since it's easier to implement, the first method of adding HTML and CSS is used.
To use these templates on your own site, follow the usage instructions above and replace the bioEp.init() call with the template. Then, in the html of the template, replace #YOURURLHERE with the URL you want to redirect users to when they click the popup.
Template 1
Using HTML and CSS to create the popup.
See the Pen vEOora by Conner Hewitt (@beekerio) on CodePen.
bioEp.init({
html: '<div id="bio_ep_content">' +
'<img src="http://beeker.io/images/posts/2/tag.png" alt="Claim your discount!" />' +
'<span>HOLD ON!</span>' +
'<span>Click the button below to get a special discount</span>' +
'<span>This offer will NOT show again!</span>' +
'<a href="#YOURURLHERE" class="bio_btn">CLAIM YOUR DISCOUNT</a>' +
'</div>',
css: '#bio_ep {width: 400px; height: 300px; color: #333; background-color: #fafafa; text-align: center;}' +
'#bio_ep_content {padding: 24px 0 0 0; font-family: "Titillium Web";}' +
'#bio_ep_content span:nth-child(2) {display: block; color: #f21b1b; font-size: 32px; font-weight: 600;}' +
'#bio_ep_content span:nth-child(3) {display: block; font-size: 16px;}' +
'#bio_ep_content span:nth-child(4) {display: block; margin: -5px 0 0 0; font-size: 16px; font-weight: 600;}' +
'.bio_btn {display: inline-block; margin: 18px 0 0 0; padding: 7px; color: #fff; font-size: 14px; font-weight: 600; background-color: #70bb39; border: 1px solid #47ad0b; cursor: pointer; -webkit-appearance: none; -moz-appearance: none; border-radius: 0; text-decoration: none;}',
fonts: ['//fonts.googleapis.com/css?family=Titillium+Web:300,400,600'],
cookieExp: 0
});
Template 2
Using a premade image.
See the Pen ByNXVE by Conner Hewitt (@beekerio) on CodePen.
bioEp.init({
width: 394,
height: 298,
html: '<a href="#YOURURLHERE" title="Claim your discount!"><img src="http://beeker.io/images/posts/2/template2.png" alt="Claim your discount!" /></a>',
cookieExp: 0
});
Options
All options must be added to the init function as an object.
Name | Type | Default | Description |
---|---|---|---|
width | integer | 400 | The width of the popup. This can be overridden by adding your own CSS for the #bio_ep element. |
height | integer | 220 | The height of the popup. This can be overridden by adding your own CSS for the #bio_ep element. |
html | string | blank | The HTML code to be placed within the popup. HTML can be added through this function or on the page itself within a element. |
css | string | blank | The CSS styles for the popup. CSS can be added through this function or on the page itself. |
fonts | array | null | An array containing URLs that link to font stylesheets. Google Fonts was the main idea behind this feature. |
delay | integer | 5 | The time, in seconds, until the popup activates and begins watching for exit intent. If showOnDelay is set to true, this will be the time until the popup shows. |
showOnDelay | boolean | false | If true, the popup will show after the delay option time. If false, popup will show when a visitor moves their cursor above the document window, showing exit intent. |
cookieExp | integer | 30 | The number of days to set the cookie for. A cookie is used to track if the popup has already been shown to a specific visitor. If the popup has been shown, it will not show again until the cookie expires. A value of 0 will always show the popup. |
For example, here's how to create a simple popup the will automatically show after 10 seconds.
bioEp.init({
html: '<div id="content">A simple popup</div>',
css: '#content {font-size: 20px;}',
delay: 10,
showOnDelay: true
});
Tutorial
Now we'll cover how to create the exit intent popup in JavaScript. The entire script is coded in vanilla JavaScript, which means other libraries like jQuery do not need to be included along with it. Using pure JavaScript also ensures our footprint will be small.
So, let's get started with the basics. Here is the gutted version of our script.
window.bioEp = {
// Private variables
bgEl: {},
popupEl: {},
closeBtnEl: {},
shown: false,
overflowDefault: "visible",
transformDefault: "",
// Popup options
width: 400,
height: 220,
html: "",
css: "",
fonts: [],
delay: 5,
showOnDelay: false,
cookieExp: 30,
// Handle creating, reading, and deleting cookies
cookieManager: {
// Create a cookie
create: function() {},
// Get the value of a cookie
get: function() {},
// Delete a cookie
erase: function() {}
},
// Check for cookie
checkCookie: function() {},
// Add font stylesheets and CSS for the popup
addCSS: function() {},
// Add the popup to the page
addPopup: function() {},
// Show the popup
showPopup: function() {},
// Hide the popup
hidePopup: function() {},
// Handle scaling the popup
scalePopup: function() {},
// Load event listeners for the popup
loadEvents: function() {},
// Set user defined options for the popup
setOptions: function() {},
// Ensure the DOM has loaded
domReady: function() {}
// Initialize
init: function() {}
}
Our entire script is defined as a JavaScript object. In JavaScript, there's no true capability for creating classes. Instead, we must use an object directly as the class, or use a function which exports an object as the class. When using the first method, only one instance of the object (or class) can be and is created. In the second, multiple instances of the class can be instantiated using the new
keyword. An article on phpied.com goes over this more thoroughly.
The very first part of our script,
window.bioEp = {
creates a new object named bioEp
and attaches it to the window
object (bioEp stands for beeker.io Exit Popup, just in case that's bothering the heck out of you). If you've played around with JavaScript before, you'll be familiar with the window object. Essentially, everything in JavaScript is an object, including window. These objects are created by the browser when loading a page, which is referred to as the DOM or Document Object Model. JavaScript interacts with the page via the DOM.
We use the period in window.bioEp
to create the bioEp property in the window object, and we signify we're making it an object by using the open curly bracket {
. To access or create properties of objects in JavaScript, you must use a period.
Below this, we define our private variables and options.
// Private variables
bgEl: {},
popupEl: {},
closeBtnEl: {},
shown: false,
overflowDefault: "visible",
transformDefault: "",
// Popup options
width: 400,
height: 220,
html: "",
css: "",
fonts: [],
delay: 5,
showOnDelay: false,
cookieExp: 30,
These variables are technically just properties of our object. To assign something to the property of an object, we use a colon :
rather than an equals =
symbol.
Kinda like how there's no true classes in JavaScript, there's no real way to create private variables, especially when using object literal notation. While you can define "private" variables and methods inside the constructor when using the function method to create a class, this still works differently than the standard of other languages. We're simply labeling these variables as private because only our bioEp functions will be using them.
We also set the default options for the popup here, which are used in various parts of the script. Later in the code, via the setOptions
function, we'll check for any user defined options and assign them accordingly.
The rest of the code contains the various functions we'll use to create and handle the popup. Just like we can assign values and strings to the property of an object, we can also assign functions.
Let's start with the main function, init
.
The init Function
// Initialize
init: function(opts) {
// Once the DOM has fully loaded
this.domReady(function() {
// Handle options
if(typeof opts !== 'undefined')
bioEp.setOptions(opts);
// Handle the cookie
if(bioEp.checkCookie()) return;
// Add the CSS
bioEp.addCSS();
// Add the popup
bioEp.addPopup();
// Load events
setTimeout(function() {
bioEp.loadEvents();
if(bioEp.showOnDelay)
bioEp.showPopup();
}, bioEp.delay * 1000);
});
}
This is the main function of our bioEp object. All of the other main functions in the script used to create the popup, add the CSS to the page, etc are called from within this function. It's essentially the main control for our entire script. If this function is never called, our script will never run and the popup will never show or even be placed on the page.
First, we use our domReady
function to ensure that our code is only executed once the DOM has loaded.
this.domReady(function() {
Functions in our script must have access to the body element of the page. If we ran our script without waiting for the DOM to fully load, and our script is in the head element, it will run before the body element is even placed on the page. Details on the domReady
function will be explained in a new post very soon (I'll link to it here).
Inside our anonymous function is where we actually run our script. We first handle any options set by the user, if there are any, via the setOptions
function. Next, we check for a cookie that's set by the script to determine if the popup should show for this visitor or not. If the cookie is found and set to true, we exit out of the init function entirely by calling return.
The next two functions add the CSS and popup HTML to the DOM, which we'll cover a bit later. Finally, we use another anonymous function inside a setTimeout call to handle loading the event listeners we need to track mouse movement for exit intent and clicks on the close button of the popup. This function also checks if the popup should show based on the delay time rather than exit intent. You can see we use the delay
option of our object to set the time, in seconds, for the timeout call.
The setOption Function
// Set user defined options for the popup
setOptions: function(opts) {
this.width = (typeof opts.width === 'undefined') ? this.width : opts.width;
this.height = (typeof opts.height === 'undefined') ? this.height : opts.height;
this.html = (typeof opts.html === 'undefined') ? this.html : opts.html;
this.css = (typeof opts.css === 'undefined') ? this.css : opts.css;
this.fonts = (typeof opts.fonts === 'undefined') ? this.fonts : opts.fonts;
this.delay = (typeof opts.delay === 'undefined') ? this.delay : opts.delay;
this.showOnDelay = (typeof opts.showOnDelay === 'undefined') ? this.showOnDelay : opts.showOnDelay;
this.cookieExp = (typeof opts.cookieExp === 'undefined') ? this.cookieExp : opts.cookieExp;
}
We pass the options object to this function as the opts
argument. For each option, we use a single line if statement known as a ternary operator to check if the option is undefined. If it's undefined, we'll keep the default setting, otherwise we'll change it to the user specified value. There's actually a few ways of setting options like this, but we're checking for undefined for a specific reason.
One popular method is to use the 'or' logical operator, ||
. If we were to use this method to handle options, it would look like this.
this.width = opts.width || this.width;
The problem with this method is that if the width option for instance is set to 0, as the user defines, it will be ignored and use the default width instead. The reason for this is because 0 is "falsy", so it will return false and try to use the next value after the or operator, which is our default width. Our default width is "truthy" since it contains a value greater than 0, so it will be used instead.
The loadEvents Function
// Load event listeners for the popup
loadEvents: function() {
// Track mouse movements
document.addEventListener("mousemove", function(e) {
// Get current scroll position
var scroll = window.pageYOffset || document.documentElement.scrollTop;
if((e.pageY - scroll) < 7)
bioEp.showPopup();
});
// Handle the popup close button
this.closeBtnEl.addEventListener("click", function() {
bioEp.hidePopup();
});
// Handle window resizing
window.addEventListener("resize", function() {
bioEp.scalePopup();
});
}
Event listeners allow us to watch for certain actions and changes on the page. The first event listener, which we attach to the document object (since we want to track this event across the entire page), is mousemove
. Whenever the user moves their mouse on the page, our anonymous function will be called. Inside this function, we check for exit intent by seeing if the visitor's mouse is less than 7 pixels from the top of the document (the Y axis scroll position is taken into account in this calculation as well). If it is, this shows exit intent, so we call the function to show the popup.
You can also see we have an argument for the anonymous function, e
. When the event is triggered and our anonymous function is called, an event object is passed to our anonymous function as the first argument. We use the data in this object to get the mouse position on the page.
For handling the close button, we assign an event listener to the click
event, attached to the close button element. If the close button is clicked, we call the function to hide the popup.
The last event listener we assign is to the window itself, and we check for any viewport dimension changes (the viewable area within the browser) by listening for the resize
event. If the window is resized, we run the function to scale the popup.
The scalePopup Function
// Handle scaling the popup
scalePopup: function() {
var margins = { width: 40, height: 40 };
var popupSize = { width: bioEp.popupEl.offsetWidth, height: bioEp.popupEl.offsetHeight };
var windowSize = { width: window.innerWidth, height: window.innerHeight };
var newSize = { width: 0, height: 0 };
var aspectRatio = popupSize.width / popupSize.height;
// First go by width, if the popup is larger than the window, scale it
if(popupSize.width > (windowSize.width - margins.width)) {
newSize.width = windowSize.width - margins.width;
newSize.height = newSize.width / aspectRatio;
// If the height is still too big, scale again
if(newSize.height > (windowSize.height - margins.height)) {
newSize.height = windowSize.height - margins.height;
newSize.width = newSize.height * aspectRatio;
}
}
// If width is fine, check for height
if(newSize.height === 0) {
if(popupSize.height > (windowSize.height - margins.height)) {
newSize.height = windowSize.height - margins.height;
newSize.width = newSize.height * aspectRatio;
}
}
// Set the scale amount
var scaleTo = newSize.width / popupSize.width;
// If the scale ratio is 0 or is going to enlarge (over 1) set it to 1
if(scaleTo <= 0 || scaleTo > 1) scaleTo = 1;
// Save current transform style
if(this.transformDefault === "")
this.transformDefault = window.getComputedStyle(this.popupEl, null).getPropertyValue("transform");
// Apply the scale transformation
this.popupEl.style.transform = this.transformDefault + " scale(" + scaleTo + ")";
}
Easily the heaviest function code wise is the scalePopup
function. We use it to automatically scale the size of the popup so it will fit within the browser's viewable area if the window is resized. CSS3 spec offers a handy function within the transform property named scale
which allows us to do this. It's actually a really cool function, and everything inside of the popup, all text, images, etc, is scaled to size.
We first set all of the size information we'll need. The margins
that we set are taken into account with the window size, so when the popup is scaled, there will be space between it and the window. The popupSize
and windowSize
variables store the popup size and viewable window size in pixels, and the newSize
variable will store the size the popup should be scaled to, also in pixels. In order to keep the new width and height proportional, we also get the aspect ratio of the popup, stored in the aspectRatio
variable.
Now we get into actually scaling the popup. First we check the width of the popup vs the width of the viewable window and margin. If the popup is larger, we scale the width down to the proper size in pixels, then apply the proportional height according to the aspect ratio. If the height is still too big after the scale based on width, then we scale it again based on the height.
If we checked the width of the popup vs the window and it fit, we must next check the height and make sure that fits as well. If the height doesn't fit, we scale it.
The scale function of the transform property takes a multiplier value, meaning if we set it to 1, the size won't change at all, and if we set it to 0.5, the size would be halved. We get this scale value and store it in scaleTo
by dividing the new size of the popup by the original size. We also check to make sure that if it's going to be essentially invisible (scaled to 0 or less), or enlarge (scaled above 1), we set it to it's original size by setting the scale to 1.
Before we modify the transform value of our popup to add in our scale function, we first want to make sure we don't overwrite any existing transform values and functions that the user may have added. We use the getComputedStyle
function, which returns the true styles of the popup element after all the stylesheets and everything have been applied to it. It returns a CSSStyleDeclaration object containing all of the CSS properties for the element and a method for getting those properties. We use one of those methods, named getPropertyValue
, to pull the value of the transform property and store in our private variable.
Finally, we modify the transform property of the element and attach our scale function to it.
The hidePopup Function
// Hide the popup
hidePopup: function() {
this.bgEl.style.display = "none";
this.popupEl.style.display = "none";
// Set body overflow back to default to show scrollbars
document.body.style.overflow = this.overflowDefault;
}
We use this function to hide the popup if the user clicks on the close button. It's very straight forward - to hide it, we just set the display CSS property to none for both the background and the popup.
In the showPopup
function, we change the overflow style of the body so the scrollbars are hidden. This prevents the user from scrolling the window while the popup is shown. With this last line, we're setting the body overflow back to it's default value so the visitor can scroll again.
The showPopup Function
// Show the popup
showPopup: function() {
if(this.shown) return;
this.bgEl.style.display = "block";
this.popupEl.style.display = "block";
// Handle scaling
this.scalePopup();
// Save body overflow value and hide scrollbars
this.overflowDefault = document.body.style.overflow;
document.body.style.overflow = "hidden";
this.shown = true;
}
This function will show the popup to the user. We first check to see that the popup hasn't already been shown to the visitor by checking our private shown
. If we didn't check this, then the popup would be triggered every time they showed exit intent, even after they clicked the close button.
The next two lines show the background and popup by setting their display styles to "block"
. The actual elements for the background and the popup are already on the page via the addPopup
function, which we'll cover next, but they're hidden by default. This simply changes it so they show.
Remember how we used the default overflow value for the body element in the hidePopup
function above? We set it here, before we make any changes to it. Then we change it to hidden so the scrollbars are removed. When the hidePopup
function is called, the scrollbars will be shown again.
Lastly, we set our private shown
variable to true
.
The addPopup Function
// Add the popup to the page
addPopup: function() {
// Add the background div
this.bgEl = document.createElement("div");
this.bgEl.id = "bio_ep_bg";
document.body.appendChild(this.bgEl);
// Add the popup
if(document.getElementById("bio_ep"))
this.popupEl = document.getElementById("bio_ep");
else {
this.popupEl = document.createElement("div");
this.popupEl.id = "bio_ep";
this.popupEl.innerHTML = this.html;
document.body.appendChild(this.popupEl);
}
// Add the close button
this.closeBtnEl = document.createElement("div");
this.closeBtnEl.id = "bio_ep_close";
this.closeBtnEl.appendChild(document.createTextNode("X"));
this.popupEl.insertBefore(this.closeBtnEl, this.popupEl.firstChild);
}
Before we can ever show the popup, it first has to exist on the page. We use this function to do that via the DOM.
We add the background element as the last child of the body element. Next, we perform a check. If an element with bio_ep as its id already exists on the page, we tell our script to use this as the popup element. If it doesn't already exist, only then do we create it and add it ourselves. This enables the script to accept HTML code for the popup via the init options or via code that's already on the page.
Another important thing to note here is that we use the innerHTML
property to set the HTML for the popup. If we didn't use this method, we wouldn't be able to cleanly accept user specified HTML via the init options.
Finally we add the close button. We add it as the first child element of the popup so that when we set its CSS position property to absolute, it will not affect the rest of the popup content and we can position it easily.
The addCSS Function
// Add font stylesheets and CSS for the popup
addCSS: function() {
// Add font stylesheets
for(var i = 0; i < this.fonts.length; i++) {
var font = document.createElement("link");
font.href = this.fonts[i];
font.type = "text/css";
font.rel = "stylesheet";
document.head.appendChild(font);
}
// Base CSS styles for the popup
var css = document.createTextNode(
"#bio_ep_bg {display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: #000; opacity: 0.3; z-index: 10001;}" +
"#bio_ep {display: none; position: fixed; width: " + this.width + "px; height: " + this.height + "px; font-family: 'Titillium Web', sans-serif; font-size: 16px; left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); background-color: #fff; box-shadow: 0px 1px 4px 0 rgba(0,0,0,0.5); z-index: 10002;}" +
"#bio_ep_close {position: absolute; left: 100%; margin: -8px 0 0 -12px; width: 20px; height: 20px; color: #fff; font-size: 12px; font-weight: bold; text-align: center; border-radius: 50%; background-color: #5c5c5c; cursor: pointer;}" +
this.css
);
// Create the style element
var style = document.createElement("style");
style.type = "text/css";
style.appendChild(css);
// Insert it before other existing style
// elements so user CSS isn't overwritten
document.head.insertBefore(style, document.getElementsByTagName("style")[0]);
}
Here is where we add the font stylesheets, as well as our base popup and user specified styles.
Since the fonts
option is an array, we loop through it to get the value (the URL for the font stylesheet) of each element in the array. We create a new link element for each font, and add it to the head element of the page.
Next, we create a text node that contains all the CSS code for our popup. We specifically include the user's CSS code at the end so it will take precedence over our default styles.
We then create a style element with our CSS text node added to it as the child, and add it to the head element. We don't just append it as a child to the head element though, like we did with the font stylesheets. Instead, we make sure our style element is inserted before any other style elements in head by using insertBefore
. Since the order of styles as they appear on the page defines precedence, we want any styles that are already on the page from the user to take priority over the default styles. By adding our default styles before all the other style elements on the page, we can be sure of the precedence. If there are no style elements in the head when we go to add ours, it will just be appended as the last child of the head element by default.
The checkCookie Function
// Handle the bioep_shown cookie
// If present and true, return true
// If not present or false, create and return false
checkCookie: function() {
// Handle cookie reset
if(this.cookieExp <= 0) {
this.cookieManager.erase("bioep_shown");
return false;
}
// If cookie is set to true
if(this.cookieManager.get("bioep_shown") == "true")
return true;
// Otherwise, create the cookie and return false
this.cookieManager.create("bioep_shown", "true", this.cookieExp);
return false;
}
Cookies are created by the browser and are used to store information about a web page via simple name value pairs (i.e. you name a cookie and set its value). So people don't get annoyed from seeing the exit popup every time they visit a page, we use a cookie named bioep_shown to check if this visitor has already seen the popup on a prior visit.
We use our cookieManager
object which is explained in the next section (an object within an object, neat.jpg) to handle creating, getting, and deleting cookies. First, we check the value of our cookieExp
option. We use 0 or less to signify that no cookie should be placed, and if there is one, delete it. This will make the popup show for every visitor session.
If the cookie already exists and its value is set to true, we simply return true to let our script know that the popup should not display.
If the cookie doesn't exist, we create it with the defined expiration value and return false.
The cookieManager Object
// Object for handling cookies, taken from QuirksMode
// http://www.quirksmode.org/js/cookies.html
cookieManager: {
// Create a cookie
create: function(name, value, days) {
var expires = "";
if(days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
}
document.cookie = name + "=" + value + expires + "; path=/";
},
// Get the value of a cookie
get: function(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(";");
for(var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == " ") c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
}
return null;
},
// Delete a cookie
erase: function(name) {
this.create(name, "", -1);
}
}
To keep our code more organized and object oriented, we create the functions we use to handle cookies as their own object named cookieManager
. The functions used here are actually taken from the wonderful QuirksMode website, where a line by line explanation is given for each. Since they explain it much better than I can, I highly recommend you visit the site.
The Complete Script
That's it! Our script is now complete and ready for use.
window.bioEp = {
// Private variables
bgEl: {},
popupEl: {},
closeBtnEl: {},
shown: false,
overflowDefault: "visible",
transformDefault: "",
// Popup options
width: 400,
height: 220,
html: "",
css: "",
fonts: [],
delay: 5,
showOnDelay: false,
cookieExp: 30,
// Object for handling cookies, taken from QuirksMode
// http://www.quirksmode.org/js/cookies.html
cookieManager: {
// Create a cookie
create: function(name, value, days) {
var expires = "";
if(days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
}
document.cookie = name + "=" + value + expires + "; path=/";
},
// Get the value of a cookie
get: function(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(";");
for(var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == " ") c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
}
return null;
},
// Delete a cookie
erase: function(name) {
this.create(name, "", -1);
}
},
// Handle the bioep_shown cookie
// If present and true, return true
// If not present or false, create and return false
checkCookie: function() {
// Handle cookie reset
if(this.cookieExp <= 0) {
this.cookieManager.erase("bioep_shown");
return false;
}
// If cookie is set to true
if(this.cookieManager.get("bioep_shown") == "true")
return true;
// Otherwise, create the cookie and return false
this.cookieManager.create("bioep_shown", "true", this.cookieExp);
return false;
},
// Add font stylesheets and CSS for the popup
addCSS: function() {
// Add font stylesheets
for(var i = 0; i < this.fonts.length; i++) {
var font = document.createElement("link");
font.href = this.fonts[i];
font.type = "text/css";
font.rel = "stylesheet";
document.head.appendChild(font);
}
// Base CSS styles for the popup
var css = document.createTextNode(
"#bio_ep_bg {display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: #000; opacity: 0.3; z-index: 10001;}" +
"#bio_ep {display: none; position: fixed; width: " + this.width + "px; height: " + this.height + "px; font-family: 'Titillium Web', sans-serif; font-size: 16px; left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); background-color: #fff; box-shadow: 0px 1px 4px 0 rgba(0,0,0,0.5); z-index: 10002;}" +
"#bio_ep_close {position: absolute; left: 100%; margin: -8px 0 0 -12px; width: 20px; height: 20px; color: #fff; font-size: 12px; font-weight: bold; text-align: center; border-radius: 50%; background-color: #5c5c5c; cursor: pointer;}" +
this.css
);
// Create the style element
var style = document.createElement("style");
style.type = "text/css";
style.appendChild(css);
// Insert it before other existing style
// elements so user CSS isn't overwritten
document.head.insertBefore(style, document.getElementsByTagName("style")[0]);
},
// Add the popup to the page
addPopup: function() {
// Add the background div
this.bgEl = document.createElement("div");
this.bgEl.id = "bio_ep_bg";
document.body.appendChild(this.bgEl);
// Add the popup
if(document.getElementById("bio_ep"))
this.popupEl = document.getElementById("bio_ep");
else {
this.popupEl = document.createElement("div");
this.popupEl.id = "bio_ep";
this.popupEl.innerHTML = this.html;
document.body.appendChild(this.popupEl);
}
// Add the close button
this.closeBtnEl = document.createElement("div");
this.closeBtnEl.id = "bio_ep_close";
this.closeBtnEl.appendChild(document.createTextNode("X"));
this.popupEl.insertBefore(this.closeBtnEl, this.popupEl.firstChild);
},
// Show the popup
showPopup: function() {
if(this.shown) return;
this.bgEl.style.display = "block";
this.popupEl.style.display = "block";
// Handle scaling
this.scalePopup();
// Save body overflow value and hide scrollbars
this.overflowDefault = document.body.style.overflow;
document.body.style.overflow = "hidden";
this.shown = true;
},
// Hide the popup
hidePopup: function() {
this.bgEl.style.display = "none";
this.popupEl.style.display = "none";
// Set body overflow back to default to show scrollbars
document.body.style.overflow = this.overflowDefault;
},
// Handle scaling the popup
scalePopup: function() {
var margins = { width: 40, height: 40 };
var popupSize = { width: bioEp.popupEl.offsetWidth, height: bioEp.popupEl.offsetHeight };
var windowSize = { width: window.innerWidth, height: window.innerHeight };
var newSize = { width: 0, height: 0 };
var aspectRatio = popupSize.width / popupSize.height;
// First go by width, if the popup is larger than the window, scale it
if(popupSize.width > (windowSize.width - margins.width)) {
newSize.width = windowSize.width - margins.width;
newSize.height = newSize.width / aspectRatio;
// If the height is still too big, scale again
if(newSize.height > (windowSize.height - margins.height)) {
newSize.height = windowSize.height - margins.height;
newSize.width = newSize.height * aspectRatio;
}
}
// If width is fine, check for height
if(newSize.height === 0) {
if(popupSize.height > (windowSize.height - margins.height)) {
newSize.height = windowSize.height - margins.height;
newSize.width = newSize.height * aspectRatio;
}
}
// Set the scale amount
var scaleTo = newSize.width / popupSize.width;
// If the scale ratio is 0 or is going to enlarge (over 1) set it to 1
if(scaleTo <= 0 || scaleTo > 1) scaleTo = 1;
// Save current transform style
if(this.transformDefault === "")
this.transformDefault = window.getComputedStyle(this.popupEl, null).getPropertyValue("transform");
// Apply the scale transformation
this.popupEl.style.transform = this.transformDefault + " scale(" + scaleTo + ")";
},
// Load event listeners for the popup
loadEvents: function() {
// Track mouse movements
document.addEventListener("mousemove", function(e) {
// Get current scroll position
var scroll = window.pageYOffset || document.documentElement.scrollTop;
if((e.pageY - scroll) < 7)
bioEp.showPopup();
});
// Handle the popup close button
this.closeBtnEl.addEventListener("click", function() {
bioEp.hidePopup();
});
// Handle window resizing
window.addEventListener("resize", function() {
bioEp.scalePopup();
});
},
// Set user defined options for the popup
setOptions: function(opts) {
this.width = (typeof opts.width === 'undefined') ? this.width : opts.width;
this.height = (typeof opts.height === 'undefined') ? this.height : opts.height;
this.html = (typeof opts.html === 'undefined') ? this.html : opts.html;
this.css = (typeof opts.css === 'undefined') ? this.css : opts.css;
this.fonts = (typeof opts.fonts === 'undefined') ? this.fonts : opts.fonts;
this.delay = (typeof opts.delay === 'undefined') ? this.delay : opts.delay;
this.showOnDelay = (typeof opts.showOnDelay === 'undefined') ? this.showOnDelay : opts.showOnDelay;
this.cookieExp = (typeof opts.cookieExp === 'undefined') ? this.cookieExp : opts.cookieExp;
},
// Ensure the DOM has loaded
domReady: function(callback) {
(document.readyState === "interactive" || document.readyState === "complete") ? callback() : document.addEventListener("DOMContentLoaded", callback);
},
// Initialize
init: function(opts) {
// Once the DOM has fully loaded
this.domReady(function() {
// Handle options
if(typeof opts !== 'undefined')
bioEp.setOptions(opts);
// Handle the cookie
if(bioEp.checkCookie()) return;
// Add the CSS
bioEp.addCSS();
// Add the popup
bioEp.addPopup();
// Load events
setTimeout(function() {
bioEp.loadEvents();
if(bioEp.showOnDelay)
bioEp.showPopup();
}, bioEp.delay * 1000);
});
}
}
Comments
For example you could do this:
I noticed that the cookie is always set, even if the mouse never leaves this window. As a result the popup only ever show while on the first page. If the user navigates around on the website and e.g. leaves window on 3rd page, the cookie already exists and there is no popup. How can I change this?
cookieExp
option to 0 inside the init() call. This will make it so the popup shows on every page load.I wondered, is there a way to make the popup display on the click of a button aswell as on exitIntent?
Cheers, Matt
bioEp.showPopup();
Basically, you would just need to upload the bioep.min.js script to your WordPress site, then use either the
wp_enqueue_script()
WordPress function to add it in, or simply modify the header.php file of your theme and link to within thehead
tag.This is explained in more detail here https://codex.wordpress.org/Using_Javascript
Thanks for answer.
Here is the code I am trying to add :
Could you please help me out with a solution..
cookieExp
parameter. By default, it's set to 30 days, so once you see the popup once it won't show again for another 30 days unless you clear your cookies. You can have it show every time by changing this parameter to 0.In the
showPopup
function, you'll see the very first line is:if(this.shown) return;
Simply delete that entire line and it should show the popup every time a visitor goes to leave the site.
bioEp.hidePopup();
function.cookieExp
parameter is set to 0. By default, it's set to 30, which means it will show once and not show again for 30 days after that.I had just had a really good experience on a site which offered excellent products and had an attractive, functional interface. My good feelings about the company /utterly/ evaporated when I got a popup as I tried to leave. It might not be logical. I know that, rationally, it only requires a few seconds to get rid of such a popup. But, it just erodes goodwill. Instead of feeling like a respected potential customer, whose experience of their website is taken into consideration (which was conveyed through clear, useable design) I immediately feel like a number on a spreadsheet as presented at a weekly staff meeting by someone whose MBA's ink is still drying.
I know business, particularly big business, is about numbers. I know that. But, goodwill and loyalty are often undervalued until it's far too late.
bioEp.hidePopup()
function.Thanks for taking the time to build it out and share with everyone.
Cheers!
Many thanks for the script it is working fine on all browser except below IE11 like IE10,9 ..
is there any way how can I fix this issue so popup will also appear on IE 10and 9 also
Thanks in advance !!!
First time, it will show popup.
The next time, there is no popup. But the popup in HTML is visible after the footer.
I'm not sure why this happen. Can you please check?
cookieExp
option higher than 0. When it's set to 0, no cookie is stored (the cookie tracks if a visitor has seen the popup), so it will show every time. If you set it to 30 for example, it won't show again for that user for 30 days.It would be really appreciated.
Thank you
cookieExp
option to 7.Sorry it's me again :) For any strange reason, the pop is positionned outside the window: http://i.imgur.com/aC7ODOj.png
I have tried to position it with CSS with position: relative and position: absolute but it doesn't work... any clue?
Thanks a lot again!
addPopup
function, just remove this section:And remove the event listener for it in the
loadEvents
function.1) If i add script file AND CSS code to HEADER (such as in method two) but do not call popup in body (div id="bio_ep"), blank popup fires.
How do I make it NOT show unless I call
2) Popup does not always fire on exit intent (mouse off browser screen), even if I wait 5-10+ seconds. I often have to reload the page to get it to fire.
I set delay: 0, and cookieExp: 0,
AND
if(bioEp.showOnDelay)
bioEp.showPopup();
}, bioEp.delay * 10);
but there is still delay in firing or it doesn't fire at all (about 30-50% of the time)...
How do I make sure it always fires on exit intent and delay is minimal.
Thank you very much!
1. You'll have to remove the
bioEp.init()
call. If this function is called, the popup will show.2. I'm going to update the script with improved exit intent detection. This should hopefully fix these issues.
PS - I now primarily use your plugin as LOAD popup with delay - it works great. Would love to also use it for EXIT intent
Thanks for great work
PSS - you should add a Donate link [/hint]
// Track mouse movements
document.addEventListener("mousemove", function(e) {
// Get current scroll position
var scroll = window.pageYOffset || document.documentElement.scrollTop;
if((e.pageY - scroll) < 7)
bioEp.showPopup();
});
Thanks again anyway. Have a good one!
Script is excellent, and thank you very much for sharing. I tried to use the method #2, which is in my case much better to use, but there is a small problem. I have an autoresponder form and I put an image above that. Now, when I open a main website, that image (only image, without form) will show up (in the lower part of the content) for about 1 or 2 seconds and escape after that. Actually it looks funny, the offer come for 1 second and escape after that. Maybe visitor will search that through whole website, and if he want to leave, finally the magic offer is here again :).
However, is there any solution, that the image will stay hidden as should?
If the mouse is above the page (for example, resting on the address bar or browser tabs) while the user is reading the page, then they move their mouse *down* into the window, the pop-over appears. In this case, they're most likely to be about to interact with the page, rather than trying to exit.
I guess the solution would be to add some code to check whether the mouse has previously been moving below the trigger margin ( y > 7 ) before showing the pop up.
if((e.pageY - scroll) >= 7 )
wasLower = 1;
if((e.pageY - scroll) < 7 && wasLower )
bioEp.showPopup();
Thankyou
bioEp.init()
when the DOM is ready, or just have it after the script include.cookieName
, and use that variable in the cookie functions.new bioEp();
to create each new popup with its own options and cookie name).Here's a great tutorial on how to define JavaScript classes using a function.
if the cookie is set to 0, so everything goes ok
However, if I set a cookie for any time greater than 0, the popup window the next time you reload the page does not remain hidden and the whole under footer stretched the width of the page :-(
One thing I noticed is when I use HTML and CSS on the page itself and not through Javascript and I set a cookie for anything 1 or above, it shows the bio_ep div on the page instead of not showing at all. Once the cookie is cleared it loads in the popup as it should. Refresh again and it will load on the page again. The only way around this is to show the popup every single time. Is there a way around this as I would prefer a day or two before the popup shows again.
Thank you!
I actually rather like it, not really the exit intent part I'm interested, as I think you should have used mouseleave, it's more accurate.
But the simple modal system with inline css. I would like to sell a product with your modal, is it possible?
I will like to know how to change something with the Overflow. My popup is a little large and in displays of 15 inches or less the visitor cannot see the half part of my popup....
My issue is, that if try to change the Overflow from "Hidden" (which is the value you assigned when showing up the popup) to any other thing, the Close button stop working :(
Cheers, and hope to read you soon.
Is it possible to call the body div from another file that does not load with site template? or iFrame as I use that would not load till called?
1) I'd like to show the popup after a delay *OR* on exit intent, whichever comes first. Is that possible?
2) Is it possible to use this for more than one popup? Specifically, I'd like to popup a different form for current subscribers every three or four months, which would be based on a different cookie expiring.
I'm pretty impressed so far, I have it up and running and I had to learn about setting cookies first.
Van
2. The code would have to be modified quite a bit to make that possible (allow bioEp to be instantiated via new keyword, and use prototyping vs object literal notation). I'll think this over.
Thank you for the comments and I hope the script helps!