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);
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.