Send event to Google Analytics on scrolling past certain points on a page
(function($, w, d) {
function autoDetectGA() {
if (typeof _gaq !== 'undefined') {
return 'ga';
}
if (typeof ga === 'function') {
return 'ua';
}
if (typeof dataLayer !== 'undefined') {
return 'gtm';
}
return false;
}
var defaults = {
elements: [],
minHeight: 0,
offset: 0, // Not used yet
elem: 0,
type: autoDetectGA(), // set type format in which the events should be send ('ga', 'ua' and 'gtm). Support for Google Analytics, (Google) Universal Analytics, and Google Tag Manager
percentage: true,
testing: false
},
$w = $(w),
$d = $(d),
cache = [];
/*
* Plugin
*/
$.scrollDepth = function(options) {
var startTime = +new Date;
options = $.extend({}, defaults, options);
if (false === options.type) {
// in case of no analytics, revert to testing so we can see what would have been sent
typeof console !== 'undefined' && typeof console.log === 'function' && console.log('Switching to testing mode, no Google Analytics found.');
options.testing = true;
}
// Return early if document height is too small
if ( $d.height() < options.minHeight ) {
return;
}
// Establish baseline (0% scroll)
sendEvent('Percentage', 'Baseline');
/*
* Functions
*/
function sendEvent(action, label, timing) {
if (!options.testing && options.type == 'ga') {
_gaq.push(['_trackEvent', 'Scroll Depth', action, label, 1, true]);
if (arguments.length > 2) {
_gaq.push(['_trackTiming', 'Scroll Depth', action, timing, label, 100]);
}
}
else if (!options.testing && options.type == 'ua') {
ga('send', 'event', 'Scroll Depth', action, label, 1, {'nonInteraction': 1});
if (arguments.length > 2) {
ga('send', 'timing', 'Scroll Depth', action, timing, label);
}
}
else if (!options.testing && options.type == 'gtm') {
dataLayer.push({'event':'GAscroll', 'eventCategory':'Scroll Depth', 'eventAction': action, 'eventLabel': label, 'eventValue': 1, 'eventNonInteraction': true});
if (arguments.length > 2) {
dataLayer.push({'event':'GAtiming', 'eventCategory':'Scroll Depth', 'eventAction': action, 'eventLabel': label, 'eventTiming': timing});
}
}
else {
$('#console').html(action + ': ' + label + '</br>' + 'Timing: ' + timing);
}
if (options.testing) {
typeof console !== 'undefined' && typeof console.log === 'function' && console.log('Testing mode; event to be sent: [type, action, label, timing]', options.type, action, label, timing);
}
}
function calculateMarks(docHeight) {
return {
'25%' : parseInt(docHeight * 0.25, 10),
'50%' : parseInt(docHeight * 0.50, 10),
'75%' : parseInt(docHeight * 0.75, 10),
// 1px cushion to trigger 100% event in iOS
'100%': docHeight - 1
};
}
function checkMarks(marks, scrollDistance, timing) {
// Check each active mark
$.each(marks, function(key, val) {
if ( $.inArray(key, cache) === -1 && scrollDistance >= val ) {
sendEvent('Percentage', key, timing);
cache.push(key);
}
});
}
function checkElements(elements, scrollDistance, timing) {
$.each(elements, function(index, elem) {
if ( $.inArray(elem, cache) === -1 && $(elem).length ) {
if ( scrollDistance >= $(elem).offset().top ) {
sendEvent('Elements', elem, timing);
cache.push(elem);
}
}
});
}
/*
* Scroll Event
*/
$w.on('scroll.scrollDepth', function() {
/*
* We calculate document and window height on each scroll event to
* account for dynamic DOM changes.
*/
var docHeight = $d.height(),
winHeight = parseInt(w.innerHeight ? w.innerHeight : $w.height(), 10),
scrollDistance = $w.scrollTop() + winHeight,
// Offset not being used yet
offset = parseInt(winHeight * (options.offset / 100), 10),
// Recalculate percentage marks
marks = calculateMarks(docHeight),
// Timing
timing = +new Date - startTime;
// If all marks already hit, unbind scroll event
if (cache.length >= 4 + options.elements.length) {
$w.off('scroll.scrollDepth');
return;
}
// Check specified DOM elements
if (options.elements) {
checkElements(options.elements, scrollDistance, timing);
}
// Check standard marks
if (options.percentage) {
checkMarks(marks, scrollDistance, timing);
}
});
};
})( jQuery, window, document );
jQuery.scrollDepth();