track "start reading", "stop reading", "end reading" user events on articles
class ArticleScrollSpy {
constructor(params = {
debugMode: false,
callBackTime: 100, // Default time delay before checking location
readerLocation: 150, // # px before tracking a reader
wordsPerMinute: 270,
}) {
this.debugMode = params.debugMode;
this.callBackTime = params.callBackTime;
this.readerLocation = params.readerLocation;
this.wordsPerMinute = params.wordsPerMinute;
this.el_content = params.el_content;
this.onLoad();
this.addEventListeners();
}
timer = 0;
beginning = Date.now();
startScroll = false;
endContent = false;
didComplete = false;
get timeToScroll() {
return Math.round((Date.now() - this.beginning) / 1000);
}
get pageBottom() {
return window.innerHeight + window.scrollY;
}
get pageHeight() {
return document.body.clentHeight;
}
get contentHeight() {
return this.el_content.offsetHeight + this.el_content.offsetTop;
}
get contentWordsCount() {
return this.el_content.textContent.trim().split(/\s+/g).filter(word => word.length > 2).length;
}
addEventListeners() {
window.addEventListener('scroll', ::this.onScroll, false);
}
onLoad() {
this.sendGaEvent('Loaded');
}
onScroll() {
timer && clearTimeout(timer);
timer = setTimeout(() => { requestAnimationFrame(::this.trackLocation) }, this.callBackTime);
}
trackLocation() {
if (!this.startScroll && this.bottom > this.readerLocation) { // on scroll start
this.sendGaEvent('start', this.timeToScroll);
this.startScroll = true;
}
else if (!this.didComplete && this.pageBottom >= this.pageHeight) { // on page bottom
this.sendGaEvent('endPage', this.timeToScroll);
this.sendGaEvent(this.timeToScroll < 60 ? 'Scanner' : 'Reader');
this.didComplete = true;
}
else if (!this.endContent && this.pageBottom >= this.contentHeight) { // on content bottom
this.sendGaEvent('endContent');
this.endContent = true;
}
}
sendGaEvent(...params) {
if (!debugMode) {
ga('send', 'event', 'News', 'Article', ...params);
} else {
console.log(...params);
}
}
}