DEV Community

Cover image for Using Smart CSS to Time Your Wonderful Newsletter Popup
Rik Schennink
Rik Schennink

Posted on • Updated on • Originally published at

Using Smart CSS to Time Your Wonderful Newsletter Popup

In the previous article on Smart CSS we used the user's scroll position to trigger a floating navigation header.

This article was originally published on my personal blog

Let's take it a step further and see if we can use this technique to determine if a user is interested in subscribing to our newsletter. Imagine this is a newsletter on a website that presents the user with an article like the one you're reading now. The goal is to trigger the user into subscribing so we can send a notification if a new article is published in the future.

We'll assume the marketing department has us at gunpoint and is ordering us to show a popup with a subscription form. Personally, I would rather show the form beneath the article, inlined in the page, but you probably know how these things go.

How to determine the best moment to show this popup?

Imagine you're shopping in real life, you wander into a random shop, you haven't even crossed the doorstep and the shop keeper jumps in your face and asks you if you want to receive their monthly newsletter. This would be weird, right?

On the internet, the above behavior is somehow the norm.

Maybe it would be a good start to gauge the user's interest in our newsletter before we ask. How do we determine this potential interest? Let's go back to our random shop.

Suppose the following:

  • I've visited the shop before
  • I've spent some time wandering the shop
  • I've actually purchased something
  • I've had a positive interaction with the shop keeper

If a couple of these had a green light, I would still not welcome the shop keeper jumping in front of me, but I certainly would not mind them asking me at the counter, and if that would happen, I might even subscribe.

Are there equivalents to this in our online environment? I can think of a couple of data points, but do let me know in the comments if you have any other suggestions.

  • The user spent some time on the site
  • The user has been reading the page
  • The user has spent some time on the current page
  • The user has made a purchase

Translating these data points to variables

To determine if the user has visited the site before we can store the time she spent on our site in a session variable using localStorage. By calculating the time spent on each page and saving it to localStorage at a certain interval we can know how long the user has been browsing the site. And thus, we can determine how familiar the user is with our platform, are we a random shopkeeper? Or have we met before?

By measuring how far the user has scrolled down on the page we can determine if he is interacting with the site. If the page presents an article this might give us some information on how far along the article the user has read. Combine this with the time user has spent on the page and we could guestimate the user has read the article or is nearly done reading it. If the user has taken the time to read the article, then that might mean he's interested in our content, he's "purchased" our content so to speak. Of course, if you run a webshop you could keep track of actual purchases as well.

The techniques used in the demo below are mostly the same as in the earlier article. I've introduced a function that serves as an updater for the data attributes. And another addition is the stack function.

const stack = (value, step) => {
  // round the value to the nearest value divisible by step
  // if value is 37 and step is 10 the result is 30
  value = Math.floor(value / step) * step;

  // 30 will be our first value
  const parts = [value];

  // now we subtract 10 each iteration till we reach 0
  while (value > 0) {
    value -= step;

  // reverse the array so it goes from 0 to 30 insted of 30 to 0
  return parts.reverse();
Enter fullscreen mode Exit fullscreen mode

This function receives a value and a step parameter. Suppose the value is set to 50 and the step is set to 10, the function will return this array: [0, 10, 20, 30, 40, 50]. We use this array to store multiple values in our data attributes.

For example, suppose the user has scrolled 50% of the page. The data-vertical-scroll-progress attribute is then set to "0 10 20 30 40 50". This allows us to use CSS attribute selectors to determine if the user has scrolled at least 30 percent of the page. The ~= comparison checks if the set value contains in a white space separated list of items.

<html data-vertical-scroll-progress="0 10 20 30 40 50">
    <!-- Your website here -->
Enter fullscreen mode Exit fullscreen mode
html[data-vertical-scroll-progress~='30'] {
   /* user has scrolled down 30% */
Enter fullscreen mode Exit fullscreen mode

Let's combine all our tests and make this a reality. The below demo will show the popup when you've scrolled down 50% and have been on the page for 30 seconds.

I'm super interested in other ways we can use these techniques, so if you have any ideas, share them below!

Top comments (7)

link2twenty profile image
Andrew Bone

You could use the newish, Intersection Observer API. You'd need to make an element to mark the halfway point. But you could make something like this:

rikschennink profile image
Rik Schennink

Thanks, Andrew!

While IntersectionObserver is a super nice API, it's not really in line with the purpose behind the article, which is doing this by feeding info to CSS using JavaScript and not controlling the hiding and showing of elements with JavaScript. Thereby completely separating CSS from JavaScript.

bhaibel profile image
Betsy Haibel

The two approaches seem compatible to me! If the intersection observer updated the body class with a data-last-landmark attribute, instead of a raw %, you could still reference off of that with CSS.

Thread Thread
rikschennink profile image
Rik Schennink

Interesting approach! That would certainly work I think.

link2twenty profile image
Andrew Bone

I see, my apologies 🙂

Generally, I try to avoid listening on scroll because it can cause jank when scrolling (especially on mobile).

That being said, "premature optimization is the root of all evil" 😅


Thread Thread
rikschennink profile image
Rik Schennink

It’s something to surely take into account, that said, the previous article outlines some of the performance improvements applied in the code.

jvanbruegge profile image
Jan van Brügge

Or you could just provide an RSS feed sigh
What happened to the good, old and reliable tech...