PluginProbe
Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) / trunk
Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) vtrunk
9.1.2 9.1.1 9.1.0 9.0.2 9.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 All 153 releases
wp-analytify / assets / js / scrolldepth.js

scrolldepth.js in Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) trunk, at assets/js/scrolldepth.js

242 lines 6.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function ($) {
2
3 (function (factory) {
4 if (typeof define === 'function' && define.amd) {
5 // AMD
6 define(['jquery'], factory);
7 } else if (typeof module === 'object' && module.exports) {
8 // CommonJS
9 module.exports = factory(require('jquery'));
10 } else {
11 // Browser globals
12 factory(jQuery);
13 }
14 }(function ($) {
15
16 'use strict';
17
18 const defaults = {
19 percentage: true
20 };
21
22 const $window = $(window);
23
24 // Change these from const to let so they can be reassigned
25 let cache = [];
26 let scrollEventBound = false;
27 let lastPixelDepth = 0;
28
29 $.scrollDepth = function (options) {
30
31 const startTime = +new Date();
32
33 options = $.extend({}, defaults, options);
34
35 function sendEvent( page_link, percentage, scrollDistance, timing ) {
36 // Always use gtag for GA4 (since UA is deprecated)
37 if ( typeof gtag !== 'undefined' ) {
38 // Send scroll depth event for GA4
39 gtag('event', 'scroll_depth', {
40 'wpa_category': 'Analytify Scroll Depth',
41 'wpa_percentage': percentage,
42 'non_interaction': true
43 });
44
45 // Send timing event - always include timing data
46 const eventTiming = timing || (+new Date - startTime);
47 gtag('event', 'timing_complete', {
48 'event_category': 'Analytify Scroll Depth',
49 'event_label': page_link + ' - ' + percentage + '%',
50 'value': eventTiming,
51 'non_interaction': true
52 });
53 }
54 }
55
56 function calculateMarks(docHeight) {
57 return {
58 '25': parseInt(docHeight * 0.25, 10),
59 '50': parseInt(docHeight * 0.50, 10),
60 '75': parseInt(docHeight * 0.75, 10),
61 /* Cushion to trigger 100% event in iOS */
62 '100': docHeight - 5
63 };
64 }
65
66 function checkMarks(marks, scrollDistance, timing) {
67 /* Check each active mark */
68 $.each(marks, function (key, val) {
69 if ($.inArray(key, cache) === -1 && scrollDistance >= val) {
70 const permalink = ( typeof analytifyScroll !== 'undefined' && analytifyScroll.permalink ) ? analytifyScroll.permalink : window.location.href;
71 sendEvent(permalink, key, scrollDistance, timing);
72 cache.push(key);
73 }
74 });
75 }
76
77 function rounded(scrollDistance) {
78 /* Returns String */
79 return (Math.floor(scrollDistance / 250) * 250).toString();
80 }
81
82 function init() {
83 bindScrollDepth();
84
85 // Fire 100% event on load when content fits in the viewport
86 const docHeight = $(document).height();
87 const winHeight = window.innerHeight ? window.innerHeight : $window.height();
88 if (winHeight >= docHeight) {
89 const timing = +new Date - startTime;
90 const permalink = ( typeof analytifyScroll !== 'undefined' && analytifyScroll.permalink ) ? analytifyScroll.permalink : window.location.href;
91 // Avoid duplicate 100% event
92 if ($.inArray('100', cache) === -1) {
93 sendEvent(permalink, '100', docHeight, timing);
94 cache.push('100');
95 }
96 }
97 }
98
99
100 /* Reset Scroll Depth with the originally initialized options */
101 $.scrollDepth.reset = function () {
102 cache = [];
103 lastPixelDepth = 0;
104 $window.off('scroll.scrollDepth');
105 bindScrollDepth();
106 };
107
108 /* Add DOM elements to be tracked */
109 $.scrollDepth.addElements = function (elems) {
110
111 if (typeof elems == 'undefined' || ! $.isArray(elems)) {
112 return;
113 }
114
115 $.merge(options.elements, elems);
116
117 /* If scroll event has been unbound from window, rebind */
118 if (! scrollEventBound) {
119 bindScrollDepth();
120 }
121
122 };
123
124 /* Remove DOM elements currently tracked */
125 $.scrollDepth.removeElements = function (elems) {
126
127 if (typeof elems == 'undefined' || ! $.isArray(elems)) {
128 return;
129 }
130
131 $.each(elems, function (index, elem) {
132
133 const inElementsArray = $.inArray(elem, options.elements);
134 const inCacheArray = $.inArray(elem, cache);
135
136 if (inElementsArray != -1) {
137 options.elements.splice(inElementsArray, 1);
138 }
139
140 if (inCacheArray != -1) {
141 cache.splice(inCacheArray, 1);
142 }
143
144 });
145
146 };
147
148 function throttle(func, wait) {
149 let timeout = null;
150 let previous = 0;
151 return function () {
152 const context = this;
153 const args = arguments;
154 const now = new Date;
155 if (! previous) { previous = now; }
156 const remaining = wait - (now - previous);
157
158 if (remaining <= 0) {
159 clearTimeout(timeout);
160 timeout = null;
161 previous = now;
162 func.apply(context, args);
163 } else if (! timeout) {
164 timeout = setTimeout(function () {
165 previous = new Date;
166 timeout = null;
167 func.apply(context, args);
168 }, remaining);
169 }
170 };
171 }
172
173 /*
174 * Scroll Event
175 */
176
177 function bindScrollDepth() {
178
179 scrollEventBound = true;
180
181 $window.on('scroll.scrollDepth', throttle(function () {
182 /*
183 * We calculate document and window height on each scroll event to
184 * account for dynamic DOM changes.
185 */
186
187 const docHeight = $(document).height(),
188 winHeight = window.innerHeight ? window.innerHeight : $window.height(),
189 scrollDistance = $window.scrollTop() + winHeight,
190
191 /* Recalculate percentage marks */
192 marks = calculateMarks(docHeight),
193
194 /* Timing */
195 timing = +new Date - startTime;
196
197 checkMarks(marks, scrollDistance, timing);
198 }, 500));
199
200 }
201
202 init();
203 };
204
205 /* UMD export */
206 return $.scrollDepth;
207
208 }));
209
210 // Wait for gtag to be available before initializing scroll depth tracking
211 function initScrollDepth() {
212
213 // Check if gtag is available (either directly or via dataLayer)
214 if ( typeof gtag !== 'undefined' || ( typeof window.dataLayer !== 'undefined' && typeof window.dataLayer.push === 'function' ) ) {
215 $.scrollDepth();
216 } else {
217 // Retry after a short delay if gtag is not yet available (max 5 seconds)
218 const maxRetries = 50;
219 let retryCount = 0;
220 const checkInterval = setInterval( function () {
221 retryCount++;
222 if ( typeof gtag !== 'undefined' || ( typeof window.dataLayer !== 'undefined' && typeof window.dataLayer.push === 'function' ) ) {
223 clearInterval( checkInterval );
224 $.scrollDepth();
225 } else if ( retryCount >= maxRetries ) {
226 clearInterval( checkInterval );
227 // Initialize anyway - gtag might be loaded later
228 $.scrollDepth();
229 }
230 }, 100 );
231 }
232 }
233
234 // Initialize when DOM is ready
235 if ( document.readyState === 'loading' ) {
236 document.addEventListener( 'DOMContentLoaded', initScrollDepth );
237 } else {
238 initScrollDepth();
239 }
240
241 })(jQuery);
242