Fire GTM Tag Upon Scroll Depth and Time Spent

How to fire a tag in Google Tag Manager when the user has scrolled past a certain depth and spent a certain amount of time on the page. This can be done with the built-in Scroll Depth and Timer tirggers.

Over 5 years ago, I wrote an article titled Track Adjusted Bounce Rate In Universal Analytics. It basically explored a number of different methods to tweak the Bounce Rate metric so that it becomes more meaningful in your Google Analytics reports.

Now, writing that article wasn’t necessarily my proudest moment. It’s not because the solution was poor, but rather because I was suggesting it makes sense to tweak a metric. The concept of “adjusted Bounce Rate” sounds like the analyst is fixing a metric to be more beneficial to their cause, rather than fixing the business problem that caused the metric to be poor in the first place.

Anyway, the combination of time spent and scroll depth reached is great for evaluating content digestion, and in some cases it might even make sense to use this combination to make Bounce Rate more meaningful.

With the introduction of the Timer trigger and the Scroll Depth trigger, we now have a more elegant way of implementing the combination of time spent and scroll threshold reached using Google Tag Manager. In this article, I’ll show you how to do it.

The logic

The idea is simple. Knowing that in GTM, multiple triggers attached to a tag follow OR-logic, we can add both a Scroll Depth trigger and a Timer trigger to the tag, but establish a dependency between these two triggers so that the tag doesn’t execute until both of the triggers have fired.

Basically, in the Scroll Depth trigger, we also check whether the Timer trigger has fired, by querying if gtm.timerInterval has been pushed into dataLayer with a value that equals the interval we set for the Timer trigger.

Conversely, in the Timer trigger, we check if gtm.scrollDepthThreshold has been pushed into dataLayer with the threshold we are waiting for the user to scroll past.

In other words, the tag will not execute until both of these triggers have fired.

Create the variables

For this, you’ll first need to activate the Scroll Depth Threshold Built-in variable by browsing to Variables in the GTM user interface, and then clicking the red CONFIGURE button under the Built-in Variables heading.

In the overlay that opens, check the box next to Scroll Depth Threshold, like so:

Next, you’ll need to create a Data Layer variable for the key gtm.timerInterval, which returns the interval you are about to configure in a Timer trigger. For this, scroll down to User-defined Variables in the Variables user interface, and create a new variable that looks like this:

And that’s it for the variables you’ll need for this guide.

Create the triggers

You’ll need two triggers for this. First, a Scroll Depth trigger, where you establish the scroll depth threshold you want the user to scroll past for it to count as engagement. Second, a Timer trigger, where you establish the minimum amount of time the user needs to spend on the page for it to count as engagement.

Remember, it’s the combination of these two that will fire your tag.

For example, I’m going to set the scroll depth requirement to 50 percent scrolled, and I only want to fire the trigger on my article pages. In addition to this, I want the user to spend a minimum of 30 seconds on the page for the combination to count as engagement.

With this in mind, here’s what the Scroll Depth trigger will look like:

Two important things to note here. First, the Vertical Scroll Depths setting has a single value of 50 in the Percentages field. This means the trigger will fire as soon as the user scrolls past 50 percent of any page where this trigger is active on.

Second, one of the Fire this trigger when… conditions is

DLV - gtm.timerInterval equals 30000

This means that even though the Scroll Depth trigger itself fires, any tag it’s attached to will not fire unless a timer trigger has already fired, where the timer interval was 30 seconds (that’s 30000 milliseconds).

The Timer trigger looks like this:

As you can see, the Timer has a Limit of 1 (i.e. it will only fire once), and an Interval of 30000 (i.e. it will fire 30 seconds after it was activated).

The Fire this trigger when… condition for the Timer trigger is similar to the logic of the Scroll Depth trigger. When the Timer trigger fires, it will not fire any tags to which it is attached, unless a Scroll Depth trigger has laready fired with a scroll threshold of 50 (the percentage I configured in the Scroll Depth trigger).

Now, let’s add these two triggers to a tag, and perhaps the logic will become clearer.

Finalizing the setup

Create a new Google Analytics Event tag (or whatever tag you want to fire when this engagement is recorded), and add both of the two triggers you created to that tag, like so:

When you add both of the triggers to a single tag, the tag could potentially fire when either one of the triggers fires (because multiple triggers employ OR-logic).

However, in both of these two triggers, you’ve set the condition that the trigger will not fire the tag until the other trigger has already triggered.

In other words, the tag will not fire until both triggers have fired, which means it will fire for the last of the two that fires on the page.

Clear? Good.

Preview it!

Here’s what it looks like in Preview mode.

The Preview mode output is easy to interpret. The tag has only fired once on the page, even though it had two triggers. When the gtm.scrollDepth event was pushed into dataLayer, the tag did not fire, because the gtm.timer even hadn’t happened yet.

Finally, after 30 seconds, the gtm.timer event is pushed into dataLayer, the trigger finds that the Scroll Depth threshold had already been passed a long time ago, and thus the tag fires.


I hope this trick proves useful. It’s a great way to understand better how GTM’s triggers can work in unison.

I still wouldn’t hinge my entire content engagement measurement plan around this (there are far more useful ways to measure engagement), but it can definitely be a great way to make your Scroll depth trigger more meaningful (or, conversely, your Timer trigger more useful).

What do you think of this solution? Do you have suggestions for how to improve it? Sound off in the comments, please!