PageSourceSearch

https://www.macularsociety.org/js/content-consumption.js

js macularsociety.org collected 2026-10-02 03:41:51 UTC 5,304 bytes, 183 lines download raw bytes

1// Variables to change
2var contentContainer = 'main';//'#content';
3var endOfContent = '#site-footer';
4var wordsPerMinute = 250;
5var kp_cc_debug = 1; // optional, change to 1 if you wish to see debug messages in the console
6
7// DON'T CHANGE ANYTHING BELOW  
8// Set Variables
9var contentStarted = false;
10var pageViewed = false;
11var pageRead = false;
12var kp_cc_error = false;
13
14var event_end_of_content = 'content_scroll';
15var event_timer_reached = 'content_dwell';
16var event_viewed_read = 'content_consumed';
17
18// Calculated Variables
19var contentEl = document.querySelector(contentContainer);
20var endOfContentEl = document.querySelector(endOfContent);
21
22if (contentEl == null) {
23    console.error('KP Content Consumption: Content Container not found. Please check your settings and ensure a valid selector is provided.');
24    kp_cc_error = true;
25}
26
27if (endOfContentEl == null) {
28    console.error('KP Content Consumption: Content End Element not found. Please check your settings and ensure a valid selector is provided.');
29    kp_cc_error = true;
30}
31
32/**
33 * Start content consumption plugin
34 */
35function initContentConsumption() {
36    
37    var contentEl = document.querySelectorAll(contentContainer);
38    var wordContentCount = wordCount(contentEl);
39
40     // Count all words in content
41    if (kp_cc_debug) {
42        console.log('Content Container: ' + contentContainer);
43        console.log('Content End Element: ' + endOfContent);
44        console.log('Word Count: ' + wordContentCount);
45    }
46
47    if (contentEl && wordContentCount) {
48        // Time to read by wordsPerMinute
49        var timeToRead = wordContentCount / wordsPerMinute;
50        var interval = timeToRead * 60 * 1000;
51        var intervalFast = interval * .75;
52
53         if (kp_cc_debug) {
54            console.log('Time to read: ' + timeToRead + ' mins, Interval:' + interval + ' seconds, Interval fast (not used):' + intervalFast + ' seconds' );
55        }
56
57        pageReadTimer(interval, intervalFast);
58    }
59}
60
61function pageReadTimer(interval, intervalFast) {
62    window.setTimeout(function() {
63        if (kp_cc_debug)
64            console.log('Content Timer Completed');
65
66        window.dataLayer.push({ 'event': event_timer_reached });
67        pageRead = true;
68        contentConsumptionPushUpdate();
69    }, interval);
70}
71
72function hasReachedEndofContent(direction) {
73    // Detect Content Start
74    if (contentStarted === false) {
75        startContentObserver.observe(contentEl)
76    }
77
78    if (contentStarted) {
79        // scrolled to end of content
80        if (pageViewed === false) {
81            endContentObserver.observe(endOfContentEl)
82        }
83    }
84}
85
86function checkContentReader() {
87    if (pageRead === true && pageViewed === true) {
88        // consumers
89        window.dataLayer.push({ 'event': event_viewed_read });
90    }
91}
92
93function wordCount(words) {
94    var count = 0;
95    for (var i = 0; i < words.length; i++) {
96        var cleanWords = words[i].innerText.replace(/\n\n/g, " ");
97        cleanWords = cleanWords.replace(/\n|\t/g, " ");
98        count += cleanWords.split(" ").length;
99    }
100    return count;
101}
102
103// Scroll Function
104var scroll_pos = 0;
105var last_scroll_pos = 0;
106var ticking = false;
107
108var startContentObserver = new IntersectionObserver(function (entries, observer) {
109    entries.forEach(function (entry) {
110        if (entry.isIntersecting) {
111            if (kp_cc_debug)
112                console.log('Content Started');
113
114            contentStarted = true;
115
116            observer.unobserve(entry.target)
117        }
118    })
119}, {
120    threshold: .1
121})
122
123var endContentObserver = new IntersectionObserver(function (entries, observer) {
124    entries.forEach(function (entry) {
125        if (entry.isIntersecting) {
126            if (kp_cc_debug)
127                console.log('Content Ended');
128
129            window.dataLayer.push({ 'event': event_end_of_content });
130            pageViewed = true;
131
132            observer.unobserve(entry.target)
133        }
134    })
135}, {
136    threshold: .1
137})
138
139function getDirection(scroll_pos, last_scroll_pos) {
140    if (scroll_pos > last_scroll_pos) {
141        return 'down';
142    } else if (scroll_pos < last_scroll_pos) {
143        return 'up';
144    }
145}
146
147initContentConsumption();
148
149if (contentContainer && endOfContent && kp_cc_error !== true) {
150    window.addEventListener('scroll', function (e) {
151        scroll_pos = window.scrollY;
152        if (!ticking) {
153            window.requestAnimationFrame(function () {
154                const direction = getDirection(scroll_pos, last_scroll_pos);
155                hasReachedEndofContent(direction);
156
157                ticking = false;
158                last_scroll_pos = scroll_pos;
159            })
160
161            ticking = true;
162        }
163    })
164}
165
166function contentConsumptionPushUpdate() {
167    if (pageRead === false && pageViewed === false) {
168        // bouncers
169    }
170
171    if (pageRead === false && pageViewed === true) {
172        // skimmers
173    }
174
175    if (pageRead === true && pageViewed === false) {
176        // tab hoarders
177    }
178
179    if (pageRead === true && pageViewed === true) {
180        // consumers
181        window.dataLayer.push({ 'event': event_viewed_read });
182    }
183}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.