Hacking Google Analytics: A Guide to User Level Element & Page Visibility Tracking

Data Culture
6 min readAug 24, 2021


By Neil Oliver, Senior Data Lead @ Data Culture

Photo by Gursimrat Ganda on Unsplash

Don’t get me wrong, Google Analytics is a super powerful tool that can answer a whole host of questions about user activity; however many of us have felt the pain of trying to drill down into that data to see the specifics of what our users doing, only to be limited by GA’s aggregated output. It’s like driving a Ferrari, only to be stuck in first gear.

The journey to this solution started with the desire to try and track individual user’s page views. Many of you might be thinking this can be done by default through the audience section of GA using the clientId or a custom UserId, and you are correct, but if you look a little closer, its a bit rubbish… In fact, it’s really bad.

The main reason it is so bad is that the page view timing relies on user events such as navigating to another page of your website. That means, if the user opens a page, then clicks to another tab for half an hour and comes back to your website and continues to surf, GA will report a nice long (and inaccurate) viewing time. Worse than that, if a user does do some genuine reading of a page and then closes the tab or navigates to another website, that page view information is lost (it will show up in GA with a time of 0 seconds).

Knowing that these issues would be a common problem, the team at Data Culture (that’s us!) went on the search for a solution and found this article that uses events to track every time a user had the page in view or hidden. It even does some clever magic using Google Tag Manager to create custom metrics with a summary of total visible and hidden times for each page view. It’s quite a bit of setup but it does work very well (we implemented it and give it a 👍🏻). If you are keeping your data in GA and don’t plan to try and track individual users, this is a great way forward.

The main issues with this method is that it takes quite a bit to setup, it requires combining multiple events to get a total time on page and it still doesn’t allow accurate per user tracking.

The secret to the per user tracking is quite simple, Google Analytics generates aggregate data wherever possible. Created a custom report with a date column? GA will aggregate all page views to that date. Included the hour & minute dimensions in that report? You now have more accurate results, but it will still be aggregated by the minute. GA will show you other dimensions in some reports that would help, such as clientId or SessionId however these can’t be accessed in some custom reports. The secret is to include some additional custom dimensions that can be pulled into the report. The most important one of these is a custom timestamp for each hit event. Adding the timestamp (accurate to a hundredth of a millisecond) to your custom report will stop GA aggregating the data, giving you individual events!

Getting to this point felt great, but it made us think; how far can we take this, and can we make it easier? We focused in on exactly what we wanted to track. We wanted to know how long users were looking at each page (we weren’t interested in the ‘hidden’ metric) and we would love to know what exactly on each page the users were spending the most time looking at.

Let’s get geeky…

Using a ‘classic’ Google Universal Analytics setup, and Google Tag Manager, we implemented the 4 custom dimensions that were discussed above. From there, we started to have fun with some custom Javascript…

Google Tag Manager allows us to run custom javascript on the ‘DOM Ready’ event using the ‘Custom HTML’ tag (with the help of a <script></script> tag). This allows the use of javascript to track our events and push them to the data layer. Disclaimer: The code below is a mashup of the previously linked articles plus this Mozilla article about element tracking. However, we had one HUGE caveat to overcome; GTM only supports ECMAScript 5.1. The plus side to this limitation is that it does force a backwards compatibility mindset, which is always a good thing.

So let’s start off with finding which method of detecting page visibility is being used.

To keep track of each element’s visibility (including the page itself), we will add additional data to the DOM. In this example, we are going to grab any elements that we have given the class of .element-tracking in the html. We could add additional elements such as all images.

The Intersection Observer API can be used to trigger an event when an element is visible to the user. It is extremely flexible and in this example we have set the event to trigger when the event is at least 75% in view. When the event is triggered we will add or remove elements from the visibleElements & previouslyVisibleElements set.

Using an event listener and the page visibility settings we captured in the first step, we can now add and remove elements from the two visibility tracking sets (created above) when the page is hidden and comes back into view.

Next, we need to update the data-totalViewTime attribute every second that the page (and any elements) are in view.

Finally, before the page unloads, we must push an object to the data layer for each element that has been viewed, and the page.

Google Tag Manager Setup

Now that we have the code complete, it’s a simple case of setting up a few things in GTM (you will already have the hang of this after setting up the 4 custom dimensions).

First, let’s setup a Custom HTML tag. There is no complex setup needed; simply click new under the tags menu, select Custom HTML as the type and copy and page in the code. You need to set the trigger to DOM Ready which is a built in trigger.

Next, we need to create three variables to capture the data that we were adding to the data layer. These are page — time — total , element — time — total and element — meta. These could be named anything you like as long as they match the keys used in the javascript. To add each variable, click new user defined variable under the variables tag, select Data Layer Variable as the type and enter the name of the variable.

Two more triggers are required to watch for events with those names being added to the data layer. Select new under the triggers menu and select the type custom event. Enter the event name gtm.element.visibility.event triggering on all custom events. Repeat this process for a second trigger called gtm.page.visibility.event.

Finally, let’s send this data to Google Analytics. We need to create two more tags, one for each of the custom triggers. Each tag is a Google Analytics: Universal Analytics type. Here is an example setup for the Element Visibility event. Repeat the same process for Page Visibility.

That’s it!

You’re done, you can relax. All that is left to do is watch the events come into GA (Behavior : Events : Top Events). Don’t forget to change the secondary dimension to your custom timestamp dimension to see the individual events, or let GA do its thing and aggregate the time spent on each page and each element. Enjoy!



Data Culture

We help organizations build data capabilities and get value from their data.