Use the Intersection Observer API For Analytics Events
Page views only tell a tiny part of the story of what your visitors are doing on your site. Modern web APIs make creating tracking for certain events much more performant than in the past, and one of those is Intersection Observer.
Let's learn what it is and how to track a few key events for your analytics. The examples shown describe key features and behaviors of this API, and will help you better understand it even if your ultimate goal for using it is not analytics related.
What is the Intersection Observer API?
As is often the case, MDN has great explainer docs for Intersection Observer, which opens with this definition:
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
Put another way: the Intersection Observer creates a watch event on the element(s) you want to observe coming in and out of the viewport (or another scrollable area you define) to allow you to attach additional events based on the element's visibility/position.
Importantly, this observation is async, so it happens off the main thread, resulting in less of a performance hit than creating similar behavior via scroll listeners (note: Intersection Observer isn't the same as a scroll listener, but it can often be swapped for similar results).
To more deeply learn about Intersection Observer, I'd also highly recommend An Explanation of How the Intersection Observer Watches.
A few quick points of importance:
- Check browser compatibility to make sure Intersection Observer meets your needs. It is still technically an experimental API.
- The Intersection Observer API will initiate the callback once the target element becomes visible and meets other option parameters.
- You may provide a
threshold
value to determine when to begin observing the intersection, where a value of0.5
begins observing when 50% of the element is in view, and a value of1
is when the entire element is in view. - A "gotcha" of the
threshold
is that it means that percentage of the element is fully within view. So, if you're using0.5
for a tall element, then on a mobile viewport you may never have 50% of it in view, resulting in never triggering the callback. - You can change the observable area's bounding box by modifying the
rootMargin
property, which allowstop right bottom left
values that reduce the area from the defined direction. For example,rootMargin: '100px 0 0 -100px
would change the area from the top to100px
from the top, and likewise100px
from the bottom. - Additional data is provided within the callback (the IntersectionObserverEntry interface), such as the boolean of
isIntersecting
, theintersectionRatio
which is how much of the element is intersecting (from0
to1
), andtime
which is the time in milliseconds from when observation started to when the callback was triggered.
Tracking When An Element Comes Into View
The is the basic usage of an Intersection Observer and may be useful for metrics such as which page sections were viewed.
In terms of analytics value - a "viewed" element doesn't mean the user received value from that element, it just means they scrolled by it. You'll want to combine this information with other analytics data such as bounce rate, outbound clicks, and other more specific goal events.
For our example event, we'll attempt to make this milestone meaningful by only tracking the event when the element crosses 50% of the viewport.
To accomplish this, we'll set the rootMargin
to 0 0 -50% 0
. This means the bounding box of the observable area only extends 50% down the viewport, so the target element must be visible above the halfway point to trigger the callback.
Note: If you are tracking something at the very start or end of your page, you will not want to adjust the *rootMargin*
- see the next section for more info.
Create An Intersection Observer
First, we'll setup a check to ensure IntersectionObserver
is available in the user's browser:
For our example, we'd like to detect when an ad becomes visible.
Here, we initiate the observer - adObserver
- and then provide it the element to observe, an element with the id
of ad
:
The IntersectionObserver
will receive an array of "entries", even when we are only attaching to one element like in this example.
So, we'll create a variable to hold only the first entry. Then, we'll ensure that it isIntersecting
, else exit the function (return
).
If it is intersecting, we can proceed to trigger the analytics event.
We also include an unobserve
event to ensure our event only triggers once; else it would continue to trigger as the user scrolled up and down past the target element. If you want that repetitive behavior, simply remove that line.
You'll then notice that rootMargin
was added as was discussed earlier into the options
portion of the IntersectionObserver
.
Here's a CodePen to help visualize how this IntersectionObserver
will work.
Tracking Reading Milestones For Halfway and End
If you have a blog of any kind, you may find value in finding out how many visitors reach reading milestones, such as the halfway point and the end of the article.
For these, we need a bit more creative way to track these points. This is because, as noted in the intro, depending on say a threshold
of 0.5
for 50% requires that entire 50% being visible within the viewport at one time. It does not mean that the visitor has scrolled past at least 50% of the element.
The other trick with writing is that it's quite unlikely that your articles are all the same length, and we want to avoid hardcoding a static value anyway to determine "halfway" or "end."
Tracking Halfway
One way to track a flexible halfway point is to add an element that can be absolutely positioned halfway down an article.
First we'll add our element, a span
with the id
of halfway
.
Then, we'll add CSS to position it halfway:
And finally, we'll create our observer. Since we know that a 1px
element should be visible regardless of viewport size, we'll set our threshold
to 1
. The rest of the code should look familiar as it matches what we used for generally observing when an element came into view.
Here's a CodePen demonstrating reaching the halfway point.
Tracking End of Article
There are a few ways to track the end of the article:
- If you have a standard element in your article template that directly follows the article, you can track based on that coming into view
- You may track when the
article > :last-child
comes into view (or slightly more specifically, something likearticle > p:last-of-type
) - Or you could add another hidden element as we did for the halfway tracker
Let's look at triggering the event based on the :last-child
coming into view.
We'll set our threshold
to 0.1
since it's definitely possible that for mobile viewports a single paragraph may not fit entirely into view.
Unfamiliar with this type of CSS selector? Check out my guide to CSS selectors
And a CodePen demonstrating reaching the :last-child
, in this case a paragraph.
Tracking Time To Read
Your analytics solution may provide a general "visit duration" type of metric. But, we can expand that by tracking additional values that give a rough "time to read" metric.
The value here would be seeing if most folx scan your article content (very short time to "read"), and how many take time to actually read it and use it as a reference. If you write tutorials like I do, this can help provide context into how much value users are getting and how they most often interact with your content.
Since we've already created the "end of article" event, it would make sense to also track this event within that observer.
As mentioned in the intro, the IntersectionObserver
also gives us a time
value, which is the time in milliseconds from when observation started to when the callback was triggered - perfect for measuring reading time!
The trick here is converting from milliseconds to something a bit more readable when you're perusing your analytics stats.
We'll convert the time to seconds, then round it to the nearest half-minute (ex. 1.5
). Or, you could round to the nearest quarter-minute or just the nearest minute. The granularity is up to you! Of course, you can always adjust after you start getting values to decide how useful this metric is for you.
Putting it all together:
And this CodePen demo will display the time once you reach the last element (adjusted to a quarter of a minute for demonstration purposes).
Alternatively, you could create buckets of time segments that you feel would provide value to simplify the returned values and make it easier to spot trends.
For example:
The Intersection Observer API has many practical use cases far beyond the analytics examples shown in this tutorial. The analytics events provided an introduction to the following key features and behaviors of this API:
- An Intersection Observer event happens off the main thread which has performance advantages vs. using scroll events for similar outcomes.
- Events can by unobserved after they’re triggered, allowing only firing events based on the observation once which was key to our analytics examples.
- The
threshold
option means the percentage of the element viewable in the observable area, which may prevent events firing as that area shrinks within a responsive design or is viewed on a smaller viewport. - The default observable area is the document root, but you can choose a different element by providing it to the
root
option. - You can alter the observable area’s dimensions by adjusting the
rootMargin
option, as we explored in the first example in which we wanted the ad to pass the halfway point within the viewport to count it as “viewed”. - Sometimes you will need to create a specific element to observe to ensure the event is triggered, like we did for the “halfway” reading milestone.
- Creating an event may require “zooming out” on what your objective is, and selecting a child element to observe instead of the parent as we did to successfully track the “end of article” reading milestone by observing the
:last-child
. - A bonus feature of the Intersection Observer API is receiving the
time
value, which enables tracking the length of time until an event was triggered measured from when the observer was initiated (by default, on completion of the script containing it being loaded) until the event triggers.