Scroll Tracking

Scroll tracking can be very helpful to track content engagement for an article or webpage.

Some interesting questions it can help you answer are: What percentage of an article has been read by a user? Has an article been completed by a user? How many users make it 50% of the way through an article? What percentage of users make it to the bottom of the page?

Using the code below, a great query for this data model would be the median of ratio_max grouped by page title. You could then see which pages have the best scroll-depth engagement.

Here’s an example that shows how to model and send events based on a user’s scrolling on a page.

Due to the nature of browsers, this recipe will only capture scroll depth for 75-85% of article reads. Many times, a session will end (e.g. person closes a tab) before an event can be tracked. The data is great for understanding patterns in scroll depth and comparing content performance, but wouldn’t be viable as a per-user source of truth.

Make sure to include YOUR_KEEN_PROJECT_ID and YOUR_KEEN_WRITE_KEY.

<html>
  <head>
    <meta charset="utf-8">
    <script src="http://d26b395fwzu5fz.cloudfront.net/keen-tracking-1.0.5.js"></script>
    <style>
      body {
        /* Demo page content; not required for regular use. */
        height: 9000px;
      }
    </style>
  </head>
  <body>
    <h1>Open your developer console</h1>
    <a href="./index.html">Test Page Unload</a>
    <script>
    /*
      Define client
    */
    var client = new Keen({
      projectId: 'YOUR_KEEN_PROJECT_ID',
      writeKey: 'YOUR_KEEN_WRITE_KEY'
    });
    // Log attempts
    client.on('recordEvent', console.log);
    Keen.debug = true;

    Keen.ready(function(){
      // Set listener to sample engagement on page unload
      // Define data model for 'page-unload' events here
      setUnloadListener(function(){
        return {
          scroll_info: sampleScrollState()
        };
      });
    });

    /*
      Define scroll-handler
    */
    var winScroll = Keen.utils.listener('window');
    var pixel = 0, pixel_max = 0;
    winScroll.on('scroll', sampleScrollState);

    /* Demo logger; not required for regular use. */
    var scrollSampler = setInterval(function(){
      console.log(sampleScrollState());
    }, 2000);

    /*
      Scroll-depth sampling
    */
    function sampleScrollState() {
      pixel = getScrollOffset() + getWindowHeight();
      if (pixel > pixel_max) {
        pixel_max = pixel;
      }
      return {
        'pixel': pixel,
        'pixel_max': pixel_max,
        'ratio': parseFloat( Number(pixel / getScrollableArea()).toFixed(2) ),
        'ratio_max': parseFloat( Number(pixel_max / getScrollableArea()).toFixed(2) )
      }
    }

    function getWindowHeight() {
      return window.innerHeight || document.documentElement.clientHeight;
    }

    function getScrollOffset() {
      return (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
    }

    function getScrollableArea() {
      var body = document.body, html = document.documentElement;
      return Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ) || null;
    }


    /*
      Interrupt unload to perform synchronous request
    */
    function setUnloadListener(dataModelHandler){
      console.log('Called: setUnloadListener()')
      var attachEvent, whichEvent;
      var handler = function(){
        handleUnloadEvent(dataModelHandler);
      };
      if (window.onpagehide || window.onpagehide === null) {
        window.addEventListener('pagehide', handler, false);
      }
      else {
        attachEvent = window.attachEvent || window.addEventListener
        whichEvent = window.attachEvent ? 'onbeforeunload' : 'beforeunload';
        attachEvent(whichEvent, handler);
      }
    }

    function handleUnloadEvent(dataModelHandler){
      var request = getXHR();
      var url = client.url('events', 'page-unload');
      if (request) {
        url += '?api_key=' + client.writeKey();
        url += '&data=' + encodeURIComponent(
          btoa(
            JSON.stringify(
              dataModelHandler()
            )
          )
        );
        request.open('GET', url, false);
        request.send(null);
      }
    }

    function getXHR() {
      if (window.XMLHttpRequest && ('file:' != window.location.protocol || !window.ActiveXObject)) {
        return new XMLHttpRequest;
      } else {
        try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) {}
        try { return new ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch(e) {}
        try { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch(e) {}
        try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {}
      }
      return false;
    }
  </script>
</body>
</html>

pixel and pixel_max are how far down a user has scrolled into the page, including the effective browser window. With pixel_max, you can see the farthest a user scrolled into the page.

ratio and ratio_max are the same but in terms of the % of total scrollable area on a page.

You could also find out how long a user is on a page by subtracting the page-unload time and page-load time.

var load_time = new Date().getTime();

// later
var time_on_page = new Date().getTime() - load_time;