Skip to content

Instantly share code, notes, and snippets.

@natebot
Last active April 4, 2023 18:43
Show Gist options
  • Save natebot/c430a2074ff045745661 to your computer and use it in GitHub Desktop.
Save natebot/c430a2074ff045745661 to your computer and use it in GitHub Desktop.
Basic attempt to
/**
* Tracking contnet reading with Google Analytics
*
* Requires jQuery and Underscores.
* Assumes Univerals Google Analytics object is available.
* 1) wrap your tracked content ( post body ) with div#content_tracking_start and div#content_tracking_end
* 2) Load this script in your footer after jQuery,Underscores, and GA.
*
* A) When the content is scrolled into the viewport we send off an event with the number of seconds between the start of the page and appearance of the content
* B) When the bottom of the content is scrolled into viewport we send off an event with nubmer of active seconds from the start of content and end of content.
* Todo: Every X number of seconds send an custom metric amount to GA. This requires an event and we are limited to the number we can fire.
*/
var ge_content_tracker = function($){
// this = window in browsers
var root = this,
$root = $(root);
// default settings
var event_namespace = 'scroll.ge_content_tracker',
page_title = document.title,
debug = false,
delay = 500,
markers = { start : '#content_tracking_start', end : '#content_tracking_end' },
interactive = false,
ert = 0;
// timings
var time = {
start : new Date().getTime(),
content_started: 0,
content_ended : 0,
};
// update default settings is gect_settings object is available
if ( typeof root.gect_settings === 'object' ){
// markers = gect_settings.markers || markers;
delay = parseInt( gect_settings.delay, 10 ) || delay;
debug = gect_settings.debug || debug;
page_title = gect_settings.title || page_title;
interactive = gect_settings.interactive || interactive;
ert = parseInt( gect_settings.ert, 10 )|| ert;
track_start = gect_settings.track_start || false;
stop_bounce = gect_settings.stop_bounce || false;
}
// only query marker elements once, cache results
var $content_start = $( markers.start ),
$content_end = $( markers.end );
// check for dependent functions
if ( typeof root._ !== 'function' )
return;
if ( typeof jQuery('body')[0].getBoundingClientRect !== 'function' )
return;
// no content markers no tracking
if( $content_start.length < 0 || $content_end.length < 0 )
return;
var is_element_in_viewport = function( $el ) {
var rect = $el[0].getBoundingClientRect();
var in_horizontal = rect.left >= 0 && rect.right <= $root.width();
var in_vertical = rect.top >= 0 && rect.bottom <= $root.height();
return in_vertical && in_horizontal;
};
var content_started = _.once( function(){
// start a tracking timer after the content appears in the viewport
engaged_timer.init({page_title: page_title});
// if the end was seen before the beginning, reset the end time
if ( time.content_ended < time.content_started ){
time.content_ended = 0;
log('reseting end time');
}
time.content_started = new Date().getTime();
// send the time that content was started to your data source
if ( time.content_started > time.start ){
var content_started_seconds = Math.round( ( time.content_started - time.start ) / 1000 );
// data object to send
var metrics_data = {
category: 'GE-Content-Tracking',
action: 'content_started',
page_title: page_title,
time_in_seconds: content_started_seconds
};
sendDataCollectorEvent(metrics_data);
}
log('Content Tracking Started');
});
var content_ended = _.once( function(){
var engaged_time = engaged_timer.getTotalTime();
engaged_timer.turnOff();
if ( engaged_time > time.content_started && time.content_started > 0 ){
var metrics_data = {
category: 'GE-Content-Tracking',
action: 'content_ended',
page_title: page_title,
time_in_seconds: engaged_time
};
sendDataCollectorEvent(metrics_data);
scroll_off(); // stop listening to scroll after end is seen
}
log('Content tracking stopped. engaged_time is : ' + engaged_time);
});
// @todo detect if page loaded not at top ( refresh'd position )
var on_scroll = function() {
if ( is_element_in_viewport( $content_start ) )
content_started();
if ( is_element_in_viewport( $content_end ) )
content_ended();
};
var scroll_off = function(){
$root.off( event_namespace );
log( 'scroll listener removed' );
};
var log = function( msg ) {
if ( debug && typeof root.console === 'object' )
console.log( msg );
};
// send of data via handlers
// @todo check for existance of global DataCollectors array
// This array is a collection of objects, each with a send_event method for talking to the metrics API ( GA, Kissmetrics, Heap, etc. )
function sendDataCollectorEvent(data) {
_.each( DataCollectors, function( collector, index ) {
collector.send_event(data);
});
}
// add scroll event listener
$root.on( event_namespace , _.throttle( on_scroll, delay ) );
return {
sendDataCollectorEvent: sendDataCollectorEvent,
};
}(jQuery);
/*
|--------------------------------------------------------------------------
| Active Time Code
|--------------------------------------------------------------------------
|
| Code based on https://github.com/robflaherty/riveted
| @todo use Underscores instead of setting intervals.
|
*/
var engaged_timer = function($) {
var started = false,
stopped = false,
turnedOff = false,
clockTime = 0,
qualifyingEventTime = 0,
startTime = new Date(),
clockTimer = null,
qualifyingEventTimer = null,
idleTimer = null,
reportInterval,
idleTimeout,
page_title;
// set up listner events that signal reader attention or inattention
function init(options) {
// Set up options and defaults
options = options || {};
page_title = options.page_title || '';
reportInterval = parseInt(options.reportInterval, 10) || 5;
idleTimeout = parseInt(options.idleTimeout, 10) || 30;
// cache selection
var $doc = $(document),
$win = $(window);
// check for these behaviors to indicate attention
$win.on('mousemove.engagement', _.throttle( trigger, 500) );
$win.on('scroll.engagement', _.throttle( trigger, 500));
$doc.on('keydown.engagement', trigger );
$doc.on('click.engagment', trigger );
// Stop engagement timing when page loses visibility listeners
$doc.on('visibilitychange.engagement', visibilityChange );
$doc.on('webkitvisibilitychange.engagement', visibilityChange );
// Try to send an event before the user leaves the page
$win.on('beforeunload.engagement', sendBeforeUnload);
}
function sendBeforeUnload() {
// quick hack for proof of concept - try to send to data to GA when someone closes a tab/window or clicks a link
ge_content_tracker.sendDataCollectorEvent({
category: 'GE-Content-Tracking',
action: 'qualifying_event',
page_title: page_title,
time_in_seconds: qualifyingEventTime
});
}
function userIdleEvent() {
ge_content_tracker.sendDataCollectorEvent({
category: 'GE-Content-Tracking',
action: 'qualifying_event',
page_title: page_title,
time_in_seconds: qualifyingEventTime
});
setIdle();
}
function setIdle() {
clearTimeout(idleTimer);
stopClock();
}
function visibilityChange() {
if (document.hidden || document.webkitHidden) {
ge_content_tracker.sendDataCollectorEvent({
category: 'GE-Content-Tracking',
action: 'qualifying_event',
page_title: page_title,
time_in_seconds: qualifyingEventTime
});
setIdle();
}
}
function clock() {
clockTime += 1;
qualifyingEventTime += 1;
}
function stopClock() {
stopped = true;
clearTimeout(clockTimer);
clearTimeout(qualifyingEventTimer);
qualifyingEventTime = 0;
}
function turnOff() {
setIdle();
turnedOff = true;
}
function turnOn() {
turnedOff = false;
}
function restartClock() {
stopped = false;
clearTimeout(clockTimer);
clockTimer = setInterval(clock, 1000);
}
function startClock() {
// Calculate seconds from start to first interaction
var currentTime = new Date();
var diff = currentTime - startTime;
// Set global
started = true;
// Start clock
clockTimer = setInterval(clock, 1000);
qualifyingEventTimer = setInterval(clock, 1000);
}
function trigger() {
if (turnedOff) {
return;
}
if (!started) {
startClock();
}
if (stopped) {
restartClock();
}
clearTimeout(idleTimer);
idleTimer = setTimeout(userIdleEvent, idleTimeout * 1000 + 100);
}
function getCurrentClockTime() {
return clockTime;
}
return {
init: init,
turnOff: turnOff,
getTotalTime: getCurrentClockTime
};
}(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment