Skip to content

Instantly share code, notes, and snippets.

@mindbreaker
Created March 30, 2016 15:40
Show Gist options
  • Save mindbreaker/8e48c40deb255a465b4cd76c1869f216 to your computer and use it in GitHub Desktop.
Save mindbreaker/8e48c40deb255a465b4cd76c1869f216 to your computer and use it in GitHub Desktop.
jquery-scrolldepth for jQuery 1.6.x
/*!
* @preserve
* jquery.scrolldepth.js | v0.8
* Copyright (c) 2015 Rob Flaherty (@robflaherty)
* Licensed under the MIT and GPL licenses.
*/
;(function ( $, window, document, undefined ) {
"use strict";
var defaults = {
minHeight: 0,
elements: [],
percentage: true,
userTiming: true,
pixelDepth: true,
nonInteraction: true,
gaGlobal: false,
gtmOverride: false
};
var $window = $(window),
cache = [],
lastPixelDepth = 0,
universalGA,
classicGA,
gaGlobal,
standardEventHandler;
/*
* Plugin
*/
$.scrollDepth = function(options) {
var startTime = +new Date;
options = $.extend({}, defaults, options);
// Return early if document height is too small
if ( $(document).height() < options.minHeight ) {
return;
}
/*
* Determine which version of GA is being used
* "ga", "__gaTracker", _gaq", and "dataLayer" are the possible globals
*/
if (options.gaGlobal) {
universalGA = true;
gaGlobal = options.gaGlobal;
} else if (typeof ga === "function") {
universalGA = true;
gaGlobal = 'ga';
} else if (typeof __gaTracker === "function") {
universalGA = true;
gaGlobal = '__gaTracker';
}
if (typeof _gaq !== "undefined" && typeof _gaq.push === "function") {
classicGA = true;
}
if (typeof options.eventHandler === "function") {
standardEventHandler = options.eventHandler;
} else if (typeof dataLayer !== "undefined" && typeof dataLayer.push === "function" && !options.gtmOverride) {
standardEventHandler = function(data) {
dataLayer.push(data);
};
}
if (options.percentage) {
// Establish baseline (0% scroll)
sendBaseline('Percentage');
} else if (options.elements) {
sendBaseline('Elements');
}
/*
* Functions
*/
/*
* Putting this in a separate function because the Baseline event may soon be removed entirely
*/
function sendBaseline(action, label) {
if (standardEventHandler) {
standardEventHandler({'event': 'ScrollDistance', 'eventCategory': 'Scroll Depth', 'eventAction': action, 'eventLabel': 'Baseline', 'eventValue': 1, 'eventNonInteraction': true });
} else {
if (universalGA) {
window[gaGlobal]('send', 'event', 'Scroll Depth', action, 'Baseline', 1, {'nonInteraction': true });
}
if (classicGA) {
_gaq.push(['_trackEvent', 'Scroll Depth', action, 'Baseline', 1, true]);
}
}
}
function sendEvent(action, label, scrollDistance, timing) {
if (standardEventHandler) {
standardEventHandler({'event': 'ScrollDistance', 'eventCategory': 'Scroll Depth', 'eventAction': action, 'eventLabel': label, 'eventValue': 1, 'eventNonInteraction': options.nonInteraction});
if (options.pixelDepth && arguments.length > 2 && scrollDistance > lastPixelDepth) {
lastPixelDepth = scrollDistance;
standardEventHandler({'event': 'ScrollDistance', 'eventCategory': 'Scroll Depth', 'eventAction': 'Pixel Depth', 'eventLabel': rounded(scrollDistance), 'eventValue': 1, 'eventNonInteraction': options.nonInteraction});
}
if (options.userTiming && arguments.length > 3) {
standardEventHandler({'event': 'ScrollTiming', 'eventCategory': 'Scroll Depth', 'eventAction': action, 'eventLabel': label, 'eventTiming': timing});
}
} else {
if (universalGA) {
window[gaGlobal]('send', 'event', 'Scroll Depth', action, label, 1, {'nonInteraction': options.nonInteraction});
if (options.pixelDepth && arguments.length > 2 && scrollDistance > lastPixelDepth) {
lastPixelDepth = scrollDistance;
window[gaGlobal]('send', 'event', 'Scroll Depth', 'Pixel Depth', rounded(scrollDistance), 1, {'nonInteraction': options.nonInteraction});
}
if (options.userTiming && arguments.length > 3) {
window[gaGlobal]('send', 'timing', 'Scroll Depth', action, timing, label);
}
}
if (classicGA) {
_gaq.push(['_trackEvent', 'Scroll Depth', action, label, 1, options.nonInteraction]);
if (options.pixelDepth && arguments.length > 2 && scrollDistance > lastPixelDepth) {
lastPixelDepth = scrollDistance;
_gaq.push(['_trackEvent', 'Scroll Depth', 'Pixel Depth', rounded(scrollDistance), 1, options.nonInteraction]);
}
if (options.userTiming && arguments.length > 3) {
_gaq.push(['_trackTiming', 'Scroll Depth', action, timing, label, 100]);
}
}
}
}
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 - 5
};
}
function checkMarks(marks, scrollDistance, timing) {
// Check each active mark
$.each(marks, function(key, val) {
if ( $.inArray(key, cache) === -1 && scrollDistance >= val ) {
sendEvent('Percentage', key, scrollDistance, 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, scrollDistance, timing);
cache.push(elem);
}
}
});
}
function rounded(scrollDistance) {
// Returns String
return (Math.floor(scrollDistance/250) * 250).toString();
}
function init() {
bindScrollDepth();
}
/*
* Public Methods
*/
// Reset Scroll Depth with the originally initialized options
$.scrollDepth.reset = function() {
cache = [];
lastPixelDepth = 0;
$window.unbind('scroll.scrollDepth');
bindScrollDepth();
};
// Add DOM elements to be tracked
$.scrollDepth.elemPush = function(elems) {
if (typeof elems == "undefined") {
console.log('Current tracked elements: ' + options.elements);
return;
}
if (!$.isArray(elems)) {
console.log('Error: Method must be passed an array');
return;
}
$.merge(options.elements, elems);
};
// Remove DOM elements currently tracked
$.scrollDepth.elemRemove = function(elems) {
if (typeof elems == "undefined") {
console.log('Current tracked elements: ' + options.elements);
return;
}
if (!$.isArray(elems)) {
console.log('Error: Method must be passed an array');
return;
}
$.each(elems, function(index, elem) {
var inArray = $.inArray(elem, options.elements);
if (inArray != -1) {
options.elements.splice(inArray, 1);
}
});
};
/*
* Throttle function borrowed from:
* Underscore.js 1.5.2
* http://underscorejs.org
* (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Underscore may be freely distributed under the MIT license.
*/
function throttle(func, wait) {
var context, args, result;
var timeout = null;
var previous = 0;
var later = function() {
previous = new Date;
timeout = null;
result = func.apply(context, args);
};
return function() {
var now = new Date;
if (!previous) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
return result;
};
}
/*
* Scroll Event
*/
function bindScrollDepth() {
$window.bind('scroll.scrollDepth', throttle(function() {
/*
* We calculate document and window height on each scroll event to
* account for dynamic DOM changes.
*/
var docHeight = $(document).height(),
winHeight = window.innerHeight ? window.innerHeight : $window.height(),
scrollDistance = $window.scrollTop() + winHeight,
// 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) {
$window.unbind('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);
}
}, 500));
}
init();
};
})( jQuery, window, document );
@mindbreaker
Copy link
Author

This is a fork of the ScrollTracking library of https://github.com/robflaherty/jquery-scrolldepth working with jQuery 1.6.x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment