Last active
April 4, 2023 18:43
-
-
Save natebot/c430a2074ff045745661 to your computer and use it in GitHub Desktop.
Basic attempt to
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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