Segment Your Bounce Rate ‑ GA's Full Potential

February 24, 2014

Interaction Events and Bounce Rate

The great customizability of Google Analytics implementations can at times be a double-edged sword. We are living in the golden age of analytics and we of course we want to collect as much metadata associated with our traffic as possible. The caveat is that, with each added layer of complexity to our GA tracking, we must ensure consistency across our website. We must be especially careful that our KPIs are comparable for cross-sectional and longitudinal analysis.

  • Cross-sectional – Comparison between different landing pages
  • Longitudinal – Comparison of same landing page over time

As always, we’re going to associate this column with a real world concern; the effect of non-interaction events on bounce rate.

And as a refresher, a non-interaction event in Google Analytics does not prevent a one-page visit from being counted as a bounce. The default setting is for events to be interaction events, and to prevent a one-page visit from being counted as a bounce. At the outset, it seems a simple issue to standardize event tracking (and its effect on bounce rate) across one’s site. We either:
a. Set all our events to non-interaction, or
b. Accept that certain events are indicative of significant interaction with our site (full video play for example) and allow these to contribute to a lower bounce rate.

This approach is definitely a good start to addressing the issue of non-comparable bounce rates. And it is likely sufficient for small sites where the published content is controlled by a single individual. But for large web sites? Well, implementation and, most of all, maintenance of a standardized system of event tracking could be quite time-consuming.

Enter Google Tag Manager

If you implemented event tracking with Google Tag Mangager, switching between interaction and non-interaction events is as easy as ticking a check-box.

If you have not yet implemented your Google Analytics tracking code with Google Tag Manager, you might find my script included below helpful to your cause. And even if you do have GTM and you’re very clear on interaction versus non-interaction events, there’s still something for you!

A More-Granular Bounce Rate

Why it’s a good idea:

Not all bounces are created equal. Some bounced visits arrived at the wrong page. Others did not find their intended information on the landing page, and left. Yet others found their intended information, and left, satisfied. There are a myriad of reasons for a bounce. By better understanding the structure of our bounces, we can better understand how to optimize our landing pages. Further, the below script adds an ‘engagement’ custom dimension to the visit. So we can segment goal performance and other KPIs with a more-accurate bounce rate.

How it Works:

Instead of simply measuring bounced visits and non-bounced visits, we further partition bounced visits into those who immediately left the page, and those who looked around a bit first. We also record certain shallow interactions on the page (clicks or external link-clicks) as a category separate from deep interaction events. These deep interaction events, and/or a visit of more than one pageviews, will prevent a visit from being counted as a bounce.

Engagement Custom Dimension

    • Initially set to ‘no engagement’
    • If the visitor stays on the first page for at least 5 seconds and scrolls, it is set to ‘light engagement’
    • Likewise, if any ‘shallow interaction’ events (carousel clicks for example) are registered, it is set to ‘light engagement’. Usually this will not be an issue, since visitors will have had to remain on the page for 5 seconds to trigger shallow interaction events
    • It is set to ‘immersive engagement’ if a ‘deep interaction’ event is triggered (video play for example) or if the visitor visits a second page.


Make the following changes to the below code and then load it to your web page, following your Google Analytics pageview code snippet.

        1. If you are using Asynchronous analytics, set the universalNotAsynch variable to false
        2. UNIVERSAL ONLY – Create a custom dimension in Google Analytics. Call it ‘engagement’ and set its scope to ‘2’ (session-level)
        3. Set the variable interactDimIndex to whichever index the custom dimension/variable will occupy (1-20 for Universal and 1-5 for Asynchronous). This index was determined in the previous step for Universal. It is defined in-line for asynchronous.
        4. Optional – change cookieLast to however many minutes you want the cookie to last. It is currently set to 30 minutes.

That’s it! Enjoy.

jQuery(function ($) {
    // we will send events to set custom dimension interactionLevel
    //(session level)

    //set cookieLast to how many minutes later cookie should expire
    //pagesVisited is an arbitrary name for the cookie
    var cookieLast = 30;

    setCookie('pagesVisited', cookieLast);

    var pageNum = getCookie('pagesVisited');

    // Debug flag
    var debugMode = false;

    var cat, lab, act, opt, interact;
    cat = lab = act = opt = interact = '';

    //set which slot for the dimension/variable that you will use. If
    //you are using asynch custom variables this should be 1-5. Otherwise,
    //for custom dimension, it should match up with the slot (1-20) that
    //you set in custom definitions in GA
    var interactDimIndex = 7;
    var interactDim = 'dimension' + interactDimIndex;

    // set to false if you're using asynchronous tracking
    var universalNotAsynch = true;

    //flag that user has scrolled
    var scrolled = false;

    //flag for light interaction
    var lightInteracted = false;

    // Set some time variables to calculate reading time
    var d = new Date();
    var beginning = d.getTime();
    var totalTime = 0;

    //if first page visit
    if (pageNum == 1) {
         sendHit('set engagement', 'set bounce', 'first page loaded',''
                        , true, 'no engagement');

        window.setInterval(function () {
            var d = new Date();
            totalTime = d.getTime() - beginning;
            if (!lightInteracted && (totalTime > 5000)) {
                if (scrolled) {
                    sendHit('engagement', 'shallow interaction',
                    'scrolled and gt 5 seconds',
                        totalTime, true, 'light engagement');

                    lightInteracted = true;
        }, 501);


    //if second page of visit set immersive interaction
    if (pageNum == 2) {
        sendHit('engagement', 'shallow interaction',
        'viewed second page',
            '', false, 'immersive engagement');

    $(window).scroll(function () {
        if (scrolled === false) {
            scrolled = true;

    //if it's the third page or more of a visit we dont care

    function sendHit(cat, act, lab, opt, interact, dimString) {
        if (!debugMode) {
            if (universalNotAsynch) {
                ga('send', 'event', cat, act, lab, opt, interact,
               {interactDim: dimString});
            } else {
                _gaq.push(['_setCustomVar', interactDimIndex, cat, 2]);
                _gaq.push(['trackEvent', cat, act, lab, opt, interact]);
        } else {
            alert('event sent. values: ' + cat + ' - ' + act + ' - ' +
            lab + ' - ' + opt + ' - ' + interact);

function setCookie(cname, exminutes) {
    var d = new Date();
    d.setTime(d.getTime() + (exminutes * 60 * 1000));
    var expires = "expires=" + d.toGMTString();
    cvalue = getCookie(cname) + 1;

    document.cookie = cname + "=" + cvalue + "; " + expires+"; path=/";;

function getCookie(cname) {
    var rgx = new RegExp(cname + '=([0-9]+)');
    if ( != -1) {
        var getVal = parseInt(document.cookie.match(rgx)[1]);
        return getVal;
    return 0;