How to Build an Exit Intent Modal that helps get you hired

May 25, 2021 (3y ago)

As users, popups suck. Most of us never click on them and likely leave the page when we see them.

But, they work. They convert users to your goal better than most other techniques. In fact on my developer portfolio, resume downloads went from 0 to 10 within the first week of implementing an exit intent modal.

As you know, the job market is competitive. I've recently just transitioned careers and I know it can feel like no one is even looking at your resume. That's why I've created this tutorial, to help you get your resume into the hands of a real person. It also has the added bonus of showing the person that you know how to make popups that convert.

In this post we will learn how to build an on exit intent JavaScript modal just like the one below. If you've been following me you might notice I like to make gaming inspired web elements like my SNES controller nav bar.

But first let's quickly understand what an exit intent modal is.

What are exit intent modals (popups)?

An exit intent modal is a type of popup, that detects through JavaScript when a user is about to leave the webpage. It then displays a popup box. Normally, this popup is triggered by a JavaScript event like the users mouse moving off the document or window onto the address bar or off the browser entirely.

Generally exit intent modals are in your face and can be quite annoying. We will make sure ours is less disruptive to the user and more of a delight for the user to stumble upon. We are also going to make sure they only see it once. Because, there is nothing more annoying than seeing the same modal 10 times.

So let's get into the step by step tutorial for making this exit intent modal with HTML, CSS and JavaScript.

1. Project Setup

I have setup a GitHub repo with all the start files you are going to need to get this project up and running, fork the project here to get started.

To begin with let's understand what is happening with the HTML for this project.

<body>
  <!--modal-->
  <div id="onExitModal">
    <!-- Modal content -->
    <div class="modalContent">
      <span class="close">×</span>

      <div class="messageContainer fade-in">
        <p class="modalMessage">
          <span>it's </span>
          <span>dangerous </span>
          <span>to </span>
          <span>go </span>
          <br />
          <span>alone! </span>
          <span>take </span>
          <span>this.</span>
        </p>
      </div>

      <div class="iconContainer">
        <img src="/assets/flame.png" class="lazyload flame image" />
        <img src="/assets/oldman.svg" class="lazyload image" />
        <img src="/assets/flame.png" class="lazyload flame image" />
      </div>

      <div class="resumeContainer">
        <img class="lazyload" id="sword" src="/assets/sword.jpg" />
        <a href="#" id="resume">My Resume</a>
      </div>
    </div>
  </div>

  ...
</body>

On line 3 we have created the container for our modal and given it the id of onExitModal, this is important as we are going to target it with JavaScript later on.

Next we then create a container for our modal content. The modal content container is separated into three child containers for our message, icons and then for our resume. The key thing to note here is the message is split word by word using span elements. This will allow us to animate each word individually with CSS.

Finally, the first span element is using an x symbol so that the user can easily and intuitively close the modal, implementing this will require some JavaScript.

With just the HTML your modal should look something like the below.

all images and text for modal in html

It's not pretty, and it is not what we want. As you know, we want the modal hidden on the page until the user performed the exit intent action. Therefore, we need to implement some CSS to hide the modal. Let's look at how to do that now.

2. Adding the CSS

The CSS for this project is split into three key parts.

  1. The functional CSS
  2. The Styling CSS
  3. Animations

The functional CSS is making the modal hidden and positioning it on the page so it can appear when you implement the exit intent modals JavaScript. The styling is making it look pretty and the animation is to make the popup pop (pun intended) and to give it a video game feel.

The functional CSS

The first thing we need to do is ensure the modal is hidden from the user. Let's implement the styling for the onExitModal now.

#onExitModal {
  display: none; /* Hide the modal */
  position: fixed; /* Keep the modal in place */
  left: 0;
  top: 0;
  z-index: 99; /* Sit on top of the page */
  width: 100%; /* Make the modal full width */
  height: 100%; /* Make the modal full height */
  overflow: auto; /* Enable scroll if needed for different device sizes */
  background-color: rgb(0, 0, 0); /* Fallback color */
  background-color: rgba(0, 0, 0, 0.4);
}

Let's go through the code line by line to understand what is happening.

The display: none is ensuring that the modal is hidden by default from the user. Next, we set the position of the modal to fixed (read this guide to understand positioning) this keeps the modal on the same place on the page even if the user scrolls up and down. The last thing to take notice of is that we set the z-index to 99 this ensures that it will appear in front of all other elements on the page.

The modal will now be hidden from the users view. In due time we will implement the exit intent modals JavaScript code. But, first we need to style the rest of the modal so it looks appealing we'll start with the modalContent container.

The Styling CSS

.modalContent {
  text-align: center;
  margin: 15% auto;
  padding: 20px;
  border: 1px solid #fefefe;
  width: 80%;
  background-color: #000;
}

The styling is pretty straight forward, the main thing to take note of is the width. This tutorial won't go into media queries styling but you will need to adjust the width for different screen sizes as well as the size of the content (images, text, link).

Now that the content contain is set we can style the close element.

.close {
  color: #fefefe;
  float: right;
  font-size: 28px;
  font-weight: bold;
}

.close:hover,
.close:focus {
  color: #d4ce46;
  text-decoration: none;
  cursor: pointer;
}

The x symbol will now appear on the right of the modal and when hovered will change color to let the user know they can close the modal.

Next, we need to style the font on our message.

.modal-message {
  text-transform: uppercase;
  font-family: "Press Start 2P", cursive;
  font-size: 32px;
  color: #fff;
}

The font we are using is the Google 'Press Start 2P', you need to add the following code to your HTML header.

<link rel="preconnect" href="https://fonts.gstatic.com" />

After that, we need to style the images in the icon container as they are too big and throw off the alignment of our exit intent modal.

.image {
  width: 120px;
  height: 120px;
  padding: 20px 50px;
}

.flame {
  filter: drop-shadow(0px 0px 20px #e37d21);
}

We do a little magic with the flame elements here. Usually to give an element a shadow we would use the box-shadow property. However, this creates an ugly box effect on our images.

modal with box shadow styling making it look square

As a result, we can use the filter property with the CSS drop-shadow function to apply the effect directly to the image. It is important to note this only works with transparent images.

modal with drop-shadow on the flames giving them a glow

Obviously, this is a much better look and gives a nice effect of the flame glowing. Finally for our styling we need to style the sword element and link to your resume.

text appears word by word
.resume-container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 30px 0px 100px 0px;
}

#sword {
  width: 100px;
  height: 100px;
}

#resume {
  text-decoration: none;
  display: inline;
  font-size: 0.7em;
  padding: 20px;
  background: #7c4a4a;
  border-radius: 4%;
  color: #fff;
  text-transform: uppercase;
  font-family: "Press Start 2P", cursive;
  border: 3px solid #d4a851;
}

We have placed both elements in a flex-container so that we can position them in the center and in a column formation.

That is it for our exit intent modal styling. We now need to add the JavaScript functionality to show the modal when the user is about to exit the page.

3. Exit intent modal JavaScript

Now that you have the basic CSS set up to hide the modal from the users view, we need to set up some JavaScript to determine when to show the modal. Before we get into creating the desired effect you must first connect your JavaScript file to the HTML.

To connect the modal.js file to the HTML file. Place the below line of code within body tags at the very bottom in your HTML file.

<script src="modal.js"></script>

Now that you have connected the two files we first need to ensure we are selecting the elements we want. Open the modal.js file and add the two selectors below to the top.

const modal = document.getElementById("onExitModal");

const span = document.getElementsByClassName("close")[0];

With that done, we can start writing our exit intent modal JavaScript code to make the modal appear when a user goes to exit the page. Add the below code to your modal.js file.

document.addEventListener("mouseout", (e) => {
  if (!e.toElement && !e.relatedTarget) {
    modal.style.display = "block";
  }
});

In the above code we have added a mouseout event listener to the document. This event is fired when a the users mouse is moved so that it is no longer contained within the document. The document if you are unsure is basically the webpage.

Next we perform a conditional check to make sure there is no toElement and relatedTarget. To quickly explain these two properties the toElement property retrieves the element that the mouse pointer entered and relatedTarget property has similar functionality just with different browser support. So in plain English what that line is doing is ensuring that the mouse is no longer on any element on the page so both would be null. This essentially ensures the user is trying to exit the page.

Finally, we select the modal and change the display property from none to block which will make it appear on the screen to the user.

Great so now you have a working exit intent modal. However, it is not perfect as every time a user moves the mouse from the window the modal will appear. This will get annoying.

To fix this we want to add functionality so that it only shows once per session. We also want to add other functionality to improve the users experience with our modal so it is less spammy.

Optimizing the exit intent modals JavaScript

The first thing we are going to do to ensure the modal is only shown to the user once per session. Currently the modal will show each time the user moves the mouse outside the window.

First we are going to change the event listener to a function.

const exitEvent = (e) => {
  if (!e.toElement && !e.relatedTarget) {
    document.removeEventListener("mouseout", exitEvent);
    modal.style.display = "block";
  }
};

The benefit of doing it this way is that we can state which function we want to remove from the DOM. On line 3 we remove the event listener after the first time the user has seen it.

As a result of this change the modal will only appear once on the page. However, there is a big flaw with this. If the user leaves the page and then comes back it will appear again.

Therefore the second part of this optimization is to add some cookies to the record if the user has seen the modal within the last week. As a result, the first thing we need some cookie setter and getter functions.

const setCookie = (cname, cvalue, exdays) => {
  let expires = "";

  if (exdays) {
    let day = new Date();
    day.setTime(day.getTime() + exdays * 24 * 60 * 60 * 1000);
    expires = `expires=${day.toUTCString()}`;
  }
  document.cookie = `${cname}=${cvalue};${expires};path=/`;
};

const getCookie = (cname) => {
  let ca = document.cookie.split(";");

  for (const cookie of ca) {
    if (cookie.indexOf(cname + "=") > 0) {
      return cookie.split("=")[1];
    }
  }
  return null;
};

The scope of Cookies is beyond this post. But, at a high level the two functions will be used to check if the user has seen the modal yet with the getCookie function. If they have not we can use the setCookie function to set them so the user won't see them for a week.

With those functions written we can now update our modal.js file to check for cookies and add them once the modal has been seen.

if (!getCookie("resumeModalSeen")) {
  document.addEventListener("mouseout", exitEvent);
}

const exitEvent = (e) => {
  if (!e.toElement && !e.relatedTarget) {
    document.removeEventListener("mouseout", exitEvent);
    modal.style.display = "block";

    setCookie("resumeModalSeen", true, 7);
  }
};

In the code above we have wrapped our event listener in a conditional statement that checks if there isn't a cookie 'resumeModalSeen' then listen for the mouseout event.

Then within the exitEvent function we call the setCookie function once the modal has been seen. This will prevent the user from seeing the modal for one week.

It is important to note there are laws around cookies, in particular the GDPR guidelines. The guidelines themselves are well outside the scope of this post. But, if you implement a modal like this please ensure you are being compliant.

As a result of adding cookies and converting the event listener to a function we have solved the first issue with our modal. Next we want to ensure the user has spent sometime on our page before we let the exit intent modal show. What I mean by this is we don't want the modal to appear if a user is on our page for 1 second and then goes to exit.

To handle this we are going to wrap our event listener in a setTimeout method, this ensures the user has spent sometime on the page before the modal will even appear.

if (!getCookie("resumeModalSeen")) {
  setTimeout(() => {
    document.addEventListener("mouseout", exitEvent);
  }, 6000);
}

Above we are ensuring the event listener is only attached to the DOM after the user has been on the page for 6 seconds. Not a lot of time but enough to not make the modal annoying.

Next we want to actually optimize the exit intent. Right now if a user moves their mouse anywhere other than the window like left or right our modal will display. That is not really exit behavior. Therefore, we want to make it so that it only shows when the user moves their mouse to the top of the browser.

To ensure we only show real exit intent we are going to adjust our conditional statement in our exitEvent function.

const exitEvent = (e) => {
  if (!e.toElement && !e.relatedTarget && e.clientY < 5) {
    document.removeEventListener("mouseout", exitEvent);
    modal.style.display = "block";

    setCookie("resumeModalSeen", true, 7);
  }
};

The e.clientY < 5 condition checks the value cursors position on the window. In particular it checks the vertical position of the mouse. If that position is less than 5 then we can safely assume the user has moved the mouse towards the top of the browser and not to the left, right or bottom.

Our modal will now be appear as we want it to. But we need to give the user a way to close the modal. Without delay let's do that now.

Closing the modal

With any modal if you want the user to be less annoyed you should provide a clear way to exit it. We are going to provide the user with two common ways to exit the modal.

The first way is with a close button, which we have already added in our HTML with the span element that has the class name close. In the modal.js file just under the span selector add the following code.

span.onclick = () => {
  modal.style.display = "none";
};

In the above example we have added the onclick global event handler. When the user now clicks on the x in the modal it will display none making it appear closed.

Next we want to allow the user to close the modal by clicking anywhere else on the browser window. Add the following code to the modal.js file.

window.onclick = (e) => {
  if (e.target === modal) {
    modal.style.display = "none";
  }
};

Above we have added another onclick event handler. This time, we check to see if the user clicks on the modal. If the user clicks the modal content, that is anything within the grey border the modal won't close. But, if they click outside of the content and on the onExitModal element the modal will exit.

There you have it, that is all the JavaScript we need for our exit intent modal. Next we need to make the modal pop with some CSS animations to really grab the users attention before they leave our page without downloading our resume.

4. Add CSS Animations

Animating the exit intent modal was the best part and I encourage you to experiment with different animations on yours. If you're not familiar with animating in CSS you can checkout this guide to get started. In this post, I will just be showing you how to implement the animations without much explanation.

In this case, we are doing two different animations. In the first, we are going to make the text appear word by word like in old video games. Next we are going to display the resume and sword like they used to appear in some Zelda games.

Let's get started with the first animation.

Animating text word by word with CSS

The reason we set up our message to have each word in a separate span was to allow us to animate them one at a time.

Looking back to our HTML the messageContainer div has an additional class fadeIn. We are going to use that class to animated each of the spans. The first thing we need to do is add the animation to the CSS style definition.

.fadeIn span {
  opacity: 0;
  animation: textFadeIn 0.5s linear forwards;
  animation-delay: 0.3s;
}

In the above we have used a Combination selector which is an advanced CSS selector you can learn about here. Basically, we are telling CSS to select all spans of the parent .fadeIn class.

Next, we set the opacity to 0 so that you cannot see the span elements but they will still hold their position on the modal.

Now we add the animation. The first part textFadeIn is the animation name. Second is the length of the animation which is half a second. Third is the animation timing function we want a smooth linear function. Finally we want the styles updated to what we have at the end of the animation using the animation-fill-mode property value of forwards.

@keyframes textFadeIn {
  0% {
    opacity: 0.1;
    transform: translateX(-100px);
  }
  100% {
    opacity: 1;
    transform: translateX(0px);
  }
}

Finally, we give the animation a delay of 300 milliseconds so that it doesn't start as soon as the modal opens.

Now that we have the animation attached to the element we need to make the actual animation.

When the animation starts we have the text at 10% opactity and it is -100px on the x plane. As it transitions it will go to 0px on the x plane and full opacity. It should look like the below.

text appears on modal all at once

That is not what we want, we want it word by word. To handle this we need to target each of the span elements and add an animation-delay.

.fadeIn span:nth-of-type(2) {
  animation-delay: 0.7s;
}

.fadeIn span:nth-of-type(3) {
  animation-delay: 1.2s;
}

.fadeIn span:nth-of-type(4) {
  animation-delay: 1.7s;
}

.fadeIn span:nth-of-type(5) {
  animation-delay: 2.2s;
}

.fadeIn span:nth-of-type(6) {
  animation-delay: 2.7s;
}

.fadeIn span:nth-of-type(7) {
  animation-delay: 2.9s;
}

In the above code, we start by targeting the second span element. We then give each element a delay value that is 100ms before the end of the previous element's animation. This gives the text a nice flowing effect.

Now the text is flowing much better let's animate the resumeContainer to float up.

Animating a container to float up

The first thing we need to do is add the below lines of code to the resumeContainer styling.

{...
  opacity: 0;
  animation: resumeUp ease-out 1.5s forwards;
  animation-delay: 3.5s;
}

Again we have set the opacity to 0 so it won't appear at the start. We then add the animation like we did for the text animation. Finally we add a delay of 3.5 seconds which is about the time it takes for the text animation to finish. We do this in order to make the resume appear just after the text 'take this'. As a result the user is like "oh take this resume, sure thing!".

The last thing we need to do is create the resumeUp animation.

@keyframes resumeUp {
  from {
    opacity: 0.1;
    transform: translateY(100px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

In the above code like in the text animation we set the opacity to 10% and then as the animation progresses we increase it. This time we have set the Y position of the container to 100px and then move it towards 0 to create the floating up effect.

And boom we have a complete exit intent modal. Well done.

By and large you should now be able to create an exciting exit intent modal with HTML, CSS and JavaScript that gets your resume downloaded.

As this modal is live on my portfolio, I encourage you not to copy the modal but come up with your own. Try instead to add your own flair that matches who you are.

You can play around with it on this codepen.