Detecting Exit Intent with Vanilla JavaScript
by Christoph Schiessl on JavaScript
Visitors who find your website via Google will probably never return again. It doesn't matter how good your content is because the Internet has a whole universe of content, and any given website is like a drop in the ocean. To convert one-time visitors to repeat visitors, you have to convince them to opt-in to some communication channel so that you can reach out to them and bring more of your content to their attention.
The best way to do this is to set up a mailing list. You simply ask your visitors for their email addresses so you can message them in the future. There are other ways to do this, too, such as providing an RSS feed, but email is objectively better because it enables two-way communication. Not only can you reach your audience, but your audience can also reply to your emails to get back to you.
One of the most effective ways to ask for email addresses is through modal dialogs. You track how visitors interact with your website, and once a specific condition is met, you show a modal dialog, aka popup. The point is that popups are hard to miss, and you probably have a good chance of getting the visitor's email address if the content they've consumed so far was valuable to them.
Okay, that's enough background information. This article discusses one of the possible triggers you can use to show your popup, which is known as "exit intent" in marketing circles.
What is Exit Intent?
There's nothing you can do once a visitor has closed the browser tab or window containing your website, so you must act while your website is still open. Essentially, you do this by looking for advance indicators that usually precede the closing of your website. Hence, they suggest a visitor's intention to exit your website.
The best indicator is when a visitor's mouse pointer leaves your website's viewport because that's where the buttons to close tabs and windows are located. Technically, you can use JavaScript's mouseout
and mouseleave
events to detect just that.
Event Handlers for mouseout
and mouseleave
It's easy to create event listeners for both events and log a message to the console so that we can start experimenting with them. If you want to play with them yourself, feel free to copy the code below into a file and open it in the browser of your choice.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Exit Intent Demo</title>
<script>
function mouseleaveCallback(event) {
console.log('mouseleave');
}
function mouseoutCallback(event) {
console.log('mouseout');
}
document.addEventListener('mouseleave', mouseleaveCallback);
document.addEventListener('mouseout', mouseoutCallback);
</script>
</head>
<body>
<h1>Exit Intent Demo</h1>
</body>
</html>
Both callbacks receive an object that implements the MouseEvent
interface as a parameter, a specialization of the Event
interface. One of the properties of the Event
interface is target
, which is a reference to the element to which the event was dispatched (i.e., the recipient element of the event). In the case of mouseout
and mouseleave
, the target
is a reference to the element that the mouse pointer has left.
Additionally, and more relevant to us, the MouseEvent
interface provides a property called relatedTarget
, which is a reference to the element that the mouse pointer has entered. Note that this property is optional, meaning it can be null
, which is the case if the mouse pointer has left the viewport. Needless to say, this is precisely what we are looking for.
function mouseoutCallback(event) {
if (event.relatedTarget === null) {
console.log('exit intent => open popup');
}
}
function mouseleaveCallback(event) {
if (event.relatedTarget === null) {
console.log('exit intent => open popup');
}
}
document.addEventListener('mouseleave', mouseoutCallback);
document.addEventListener('mouseleave', mouseleaveCallback);
This code detects exit intent whenever the visitor's mouse pointer leaves the viewport, but this is probably too much and would open the popup too often. Instead, we can take the direction of the mouse pointer into account so that we can detect exit intent only if the pointer leaves the viewport in an upward direction.
The reason for this is that the buttons to close tabs and windows are usually located above the viewport. Thus, we can probably ignore it if the mouse pointer leaves the viewport in a different direction. Taking the direction into account will cause us to miss the exit intent of some visitors, but this is fine. Exit intent is a heuristic, and it was already imperfect before we took direction into account. For instance, if visitors close a tab with their keyboard, the mouse events we rely on will never be triggered.
In any case, the MouseEvent
interface has another property called clientY
that we can use. This is the Y position of the mouse pointer in viewport coordinates (i.e., it's not affected by scroll position). With that in mind, we can adjust our event listener only to detect exit intent if the mouse pointer is close to the upper boundary of the viewport, suggesting that the mouse pointer will leave the viewport in an upward direction.
function mouseoutCallback(event) {
if (event.relatedTarget === null && event.clientY <= 10) {
console.log('exit intent => open popup');
}
}
function mouseleaveCallback(event) {
if (event.relatedTarget === null && event.clientY <= 10) {
console.log('exit intent => open popup');
}
}
document.addEventListener('mouseleave', mouseoutCallback);
document.addEventListener('mouseleave', mouseleaveCallback);
That's already a pretty good implementation, but there's one more improvement to make it less intrusive to visitors. That is, we shouldn't automatically open the popup again after a visitor has manually closed it. We can use a boolean variable to ensure that we open the popup only the first time we detect exit intent.
let exitIntent = false;
function mouseoutCallback(event) {
if (!exitIntent && event.relatedTarget === null && event.clientY <= 10) {
exitIntent = true;
console.log('exit intent => open popup');
}
}
function mouseleaveCallback(event) {
if (!exitIntent && event.relatedTarget === null && event.clientY <= 10) {
exitIntent = true;
console.log('exit intent => open popup');
}
}
document.addEventListener('mouseleave', mouseoutCallback);
document.addEventListener('mouseleave', mouseleaveCallback);
We don't need two event listeners, but the question is which one to keep. Both work, for sure, but they are redundant. To make an educated decision about which to keep, you must first understand the difference between them.
What's the difference between mouseout
and mouseleave
?
The mouseout
and mouseleave
are similar but have slightly different semantics.
Contrary to mouseout
events, mouseleave
events don't bubble up to parent elements in the DOM. So, event listeners such as someElement.addEventListener( 'mouseout', function(e) {} )
get called much more often because they receive events from descendent elements that have bubbled up. Every time the mouse pointer leaves one descendent element and enters another, the event handler is called.
On the other hand, the event listener someElement.addEventListener( 'mouseleave', function(e) {} )
is only called when the mouse pointer leaves someElement
. If the pointer moves from one descendant to another, it doesn't call the event handler.
mouseleave
is better for Exit Intent
I recommend mouseleave
for exit intent because we don't care about the mouse pointer moving between descendent elements. You would need to filter out the events that bubbled up anyway because they're, by definition, not indicative of exit intent.
It makes no sense to listen for all of these events if you ignore most of them anyway. Again, both listeners work, but from a performance point of view, it's better to have event listeners that are called less often.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<script>
let exitIntent = false;
function trackExitIntent(event) {
if (!exitIntent && event.relatedTarget === null && event.clientY <= 10) {
exitIntent = true;
console.log('exit intent => open popup');
}
}
document.addEventListener('mouseleave', trackExitIntent);
</script>
<title>Exit Intent Demo</title>
</head>
<body>
<h1>Exit Intent Demo</h1>
</body>
</html>
There you have it. This is a solid trigger for opening popups to prompt visitors to subscribe to your mailing list before leaving your website. Thank you very much for reading, and see you soon! Also, don't forget to subscribe to my mailing list if you found this article interesting and have learned something new.