1(function( factory ) { 2 if (typeof define !== 'undefined' && define.amd) { 3 define([], factory); 4 } else if (typeof module !== 'undefined' && module.exports) { 5 module.exports = factory(); 6 } else { 7 window.scrollMonitor = factory(); 8 } 9})(function() { 10 11 var scrollTop = function() { 12 return window.pageYOffset || 13 (document.documentElement && document.documentElement.scrollTop) || 14 document.body.scrollTop; 15 }; 16 17 var exports = {}; 18 19 var watchers = []; 20 21 var VISIBILITYCHANGE = 'visibilityChange'; 22 var ENTERVIEWPORT = 'enterViewport'; 23 var FULLYENTERVIEWPORT = 'fullyEnterViewport'; 24 var EXITVIEWPORT = 'exitViewport'; 25 var PARTIALLYEXITVIEWPORT = 'partiallyExitViewport'; 26 var LOCATIONCHANGE = 'locationChange'; 27 var STATECHANGE = 'stateChange'; 28 29 var eventTypes = [ 30 VISIBILITYCHANGE, 31 ENTERVIEWPORT, 32 FULLYENTERVIEWPORT, 33 EXITVIEWPORT, 34 PARTIALLYEXITVIEWPORT, 35 LOCATIONCHANGE, 36 STATECHANGE 37 ]; 38 39 var defaultOffsets = {top: 0, bottom: 0}; 40 41 var getViewportHeight = function() { 42 return window.innerHeight || document.documentElement.clientHeight; 43 }; 44 45 var getDocumentHeight = function() { 46 // jQuery approach 47 // whichever is greatest 48 return Math.max( 49 document.body.scrollHeight, document.documentElement.scrollHeight, 50 document.body.offsetHeight, document.documentElement.offsetHeight, 51 document.documentElement.clientHeight 52 ); 53 }; 54 55 exports.viewportTop = null; 56 exports.viewportBottom = null; 57 exports.documentHeight = null; 58 exports.viewportHeight = getViewportHeight(); 59 60 var previousDocumentHeight; 61 var latestEvent; 62 63 var calculateViewportI; 64 function calculateViewport() { 65 exports.viewportTop = scrollTop(); 66 exports.viewportBottom = exports.viewportTop + exports.viewportHeight; 67 exports.documentHeight = getDocumentHeight(); 68 if (exports.documentHeight !== previousDocumentHeight) { 69 calculateViewportI = watchers.length; 70 while( calculateViewportI-- ) { 71 watchers[calculateViewportI].recalculateLocation(); 72 } 73 previousDocumentHeight = exports.documentHeight; 74 } 75 } 76 77 function recalculateWatchLocationsAndTrigger() { 78 exports.viewportHeight = getViewportHeight(); 79 calculateViewport(); 80 updateAndTriggerWatchers(); 81 } 82 83 var recalculateAndTriggerTimer; 84 function debouncedRecalcuateAndTrigger() { 85 clearTimeout(recalculateAndTriggerTimer); 86 recalculateAndTriggerTimer = setTimeout( recalculateWatchLocationsAndTrigger, 100 ); 87 } 88 89 var updateAndTriggerWatchersI; 90 function updateAndTriggerWatchers() { 91 // update all watchers then trigger the events so one can rely on another being up to date. 92 updateAndTriggerWatchersI = watchers.length; 93 var i = 0; 94 while( i < updateAndTriggerWatchersI ) { 95 watchers[i].update(); 96 i++; 97 } 98 99 updateAndTriggerWatchersI = watchers.length; 100 var j = 0, 101 newLength; 102 while( j < updateAndTriggerWatchersI ) { 103 watchers[j].triggerCallbacks(); 104 105 newLength = watchers.length; 106 107 if (newLength < updateAndTriggerWatchersI) { 108 j -= updateAndTriggerWatchersI - newLength; 109 } 110 111 updateAndTriggerWatchersI = newLength; 112 j++; 113 } 114 115 } 116 117 function ElementWatcher( watchItem, offsets ) { 118 var self = this; 119 120 this.watchItem = watchItem; 121 122 this.uid = Math.random().toString(36).substring(8); 123 124 if (!offsets) { 125 this.offsets = defaultOffsets; 126 } else if (offsets === +offsets) { 127 this.offsets = {top: offsets, bottom: offsets}; 128 } else { 129 this.offsets = { 130 top: offsets.top || defaultOffsets.top, 131 bottom: offsets.bottom || defaultOffsets.bottom 132 }; 133 } 134 135 this.callbacks = {}; // {callback: function, isOne: true } 136 137 for (var i = 0, j = eventTypes.length; i < j; i++) { 138 self.callbacks[eventTypes[i]] = []; 139 } 140 141 this.locked = false; 142 143 var wasInViewport; 144 var wasFullyInViewport; 145 var wasAboveViewport; 146 var wasBelowViewport; 147 148 var listenerToTriggerListI; 149 var listener; 150 function triggerCallbackArray( listeners ) { 151 if (listeners.length === 0) { 152 return; 153 } 154 listenerToTriggerListI = listeners.length; 155 while( listenerToTriggerListI-- ) { 156 listener = listeners[listenerToTriggerListI]; 157 listener.callback.call( self, latestEvent ); 158 if (listener.isOne) { 159 listeners.splice(listenerToTriggerListI, 1); 160 } 161 } 162 } 163 this.triggerCallbacks = function triggerCallbacks() { 164 165 if (this.isInViewport && !wasInViewport) { 166 triggerCallbackArray( this.callbacks[ENTERVIEWPORT] ); 167 } 168 if (this.isFullyInViewport && !wasFullyInViewport) { 169 triggerCallbackArray( this.callbacks[FULLYENTERVIEWPORT] ); 170 } 171 172 173 if (this.isAboveViewport !== wasAboveViewport && 174 this.isBelowViewport !== wasBelowViewport) { 175 176 triggerCallbackArray( this.callbacks[VISIBILITYCHANGE] ); 177 178 // if you skip completely past this element 179 if (!wasFullyInViewport && !this.isFullyInViewport) { 180 triggerCallbackArray( this.callbacks[FULLYENTERVIEWPORT] ); 181 triggerCallbackArray( this.callbacks[PARTIALLYEXITVIEWPORT] ); 182 } 183 if (!wasInViewport && !this.isInViewport) { 184 triggerCallbackArray( this.callbacks[ENTERVIEWPORT] ); 185 triggerCallbackArray( this.callbacks[EXITVIEWPORT] ); 186 } 187 } 188 189 if (!this.isFullyInViewport && wasFullyInViewport) { 190 triggerCallbackArray( this.callbacks[PARTIALLYEXITVIEWPORT] ); 191 } 192 if (!this.isInViewport && wasInViewport) { 193 triggerCallbackArray( this.callbacks[EXITVIEWPORT] ); 194 } 195 if (this.isInViewport !== wasInViewport) { 196 triggerCallbackArray( this.callbacks[VISIBILITYCHANGE] ); 197 } 198 switch( true ) { 199 case wasInViewport !== this.isInViewport: 200 case wasFullyInViewport !== this.isFullyInViewport: 201 case wasAboveViewport !== this.isAboveViewport: 202 case wasBelowViewport !== this.isBelowViewport: 203 triggerCallbackArray( this.callbacks[STATECHANGE] ); 204 } 205 206 wasInViewport = this.isInViewport; 207 wasFullyInViewport = this.isFullyInViewport; 208 wasAboveViewport = this.isAboveViewport; 209 wasBelowViewport = this.isBelowViewport; 210 211 }; 212 213 this.recalculateLocation = function() { 214 if (this.locked) { 215 return; 216 } 217 var previousTop = this.top; 218 var previousBottom = this.bottom; 219 if (this.watchItem.nodeName) { // a dom element 220 var cachedDisplay = this.watchItem.style.display; 221 if (cachedDisplay === 'none') { 222 this.watchItem.style.display = ''; 223 } 224 225 var boundingRect = this.watchItem.getBoundingClientRect(); 226 this.top = boundingRect.top + exports.viewportTop; 227 this.bottom = boundingRect.bottom + exports.viewportTop; 228 229 if (cachedDisplay === 'none') {
230 this.watchItem.style.display = cachedDisplay; 231 } 232 233 } else if (this.watchItem === +this.watchItem) { // number 234 if (this.watchItem > 0) { 235 this.top = this.bottom = this.watchItem; 236 } else { 237 this.top = this.bottom = exports.documentHeight - this.watchItem; 238 } 239 240 } else { // an object with a top and bottom property 241 this.top = this.watchItem.top; 242 this.bottom = this.watchItem.bottom; 243 } 244 245 this.top -= this.offsets.top; 246 this.bottom += this.offsets.bottom; 247 this.height = this.bottom - this.top; 248 249 if ( (previousTop !== undefined || previousBottom !== undefined) && (this.top !== previousTop || this.bottom !== previousBottom) ) { 250 triggerCallbackArray( this.callbacks[LOCATIONCHANGE] ); 251 } 252 }; 253 254 this.recalculateLocation(); 255 this.update(); 256 257 wasInViewport = this.isInViewport; 258 wasFullyInViewport = this.isFullyInViewport; 259 wasAboveViewport = this.isAboveViewport; 260 wasBelowViewport = this.isBelowViewport; 261 } 262 263 ElementWatcher.prototype = { 264 on: function( event, callback, isOne ) { 265 266 // trigger the event if it applies to the element right now. 267 switch( true ) { 268 case event === VISIBILITYCHANGE && !this.isInViewport && this.isAboveViewport: 269 case event === ENTERVIEWPORT && this.isInViewport: 270 case event === FULLYENTERVIEWPORT && this.isFullyInViewport: 271 case event === EXITVIEWPORT && this.isAboveViewport && !this.isInViewport: 272 case event === PARTIALLYEXITVIEWPORT && this.isAboveViewport: 273 callback.call( this, latestEvent ); 274 if (isOne) { 275 return; 276 } 277 } 278 279 if (this.callbacks[event]) { 280 this.callbacks[event].push({callback: callback, isOne: isOne||false}); 281 } else { 282 throw new Error('Tried to add a scroll monitor listener of type '+event+'. Your options are: '+eventTypes.join(', ')); 283 } 284 }, 285 off: function( event, callback ) { 286 if (this.callbacks[event]) { 287 for (var i = 0, item; item = this.callbacks[event][i]; i++) { 288 if (item.callback === callback) { 289 this.callbacks[event].splice(i, 1); 290 break; 291 } 292 } 293 } else { 294 throw new Error('Tried to remove a scroll monitor listener of type '+event+'. Your options are: '+eventTypes.join(', ')); 295 } 296 }, 297 one: function( event, callback ) { 298 this.on( event, callback, true); 299 }, 300 recalculateSize: function() { 301 this.height = this.watchItem.offsetHeight + this.offsets.top + this.offsets.bottom; 302 this.bottom = this.top + this.height; 303 }, 304 update: function() { 305 this.isAboveViewport = this.top < exports.viewportTop; 306 this.isBelowViewport = this.bottom > exports.viewportBottom; 307 308 this.isInViewport = (this.top <= exports.viewportBottom && this.bottom >= exports.viewportTop); 309 this.isFullyInViewport = (this.top >= exports.viewportTop && this.bottom <= exports.viewportBottom) || 310 (this.isAboveViewport && this.isBelowViewport); 311 312 }, 313 destroy: function() { 314 var index = -1, 315 self = this; 316 317 for (var i = 0; i < watchers.length; i++) { 318 if (this.uid == watchers[i].uid) { 319 index = i; 320 break; 321 } 322 } 323 324 if (index == -1) { 325 index = watchers.indexOf(this); 326 } 327 328 watchers.splice(index, 1); 329 for (var i = 0, j = eventTypes.length; i < j; i++) { 330 self.callbacks[eventTypes[i]].length = 0; 331 } 332 }, 333 // prevent recalculating the element location 334 lock: function() { 335 this.locked = true; 336 }, 337 unlock: function() { 338 this.locked = false; 339 } 340 }; 341 342 var eventHandlerFactory = function (type) { 343 return function( callback, isOne ) { 344 this.on.call(this, type, callback, isOne); 345 }; 346 }; 347 348 for (var i = 0, j = eventTypes.length; i < j; i++) { 349 var type = eventTypes[i]; 350 ElementWatcher.prototype[type] = eventHandlerFactory(type); 351 } 352 353 try { 354 calculateViewport(); 355 } catch (e) { 356 try { 357 window.$(calculateViewport); 358 } catch (e) { 359 throw new Error('If you must put scrollMonitor in the <head>, you must use jQuery.'); 360 } 361 } 362 363 function scrollMonitorListener(event) { 364 latestEvent = event; 365 calculateViewport(); 366 updateAndTriggerWatchers(); 367 } 368 369 if (window.addEventListener) { 370 window.addEventListener('scroll', scrollMonitorListener); 371 window.addEventListener('resize', debouncedRecalcuateAndTrigger); 372 } else { 373 // Old IE support 374 window.attachEvent('onscroll', scrollMonitorListener); 375 window.attachEvent('onresize', debouncedRecalcuateAndTrigger); 376 } 377 378 exports.beget = exports.create = function( element, offsets ) { 379 if (typeof element === 'string') { 380 element = document.querySelector(element); 381 } else if (element && element.length > 0) { 382 element = element[0]; 383 } 384 385 var watcher = new ElementWatcher( element, offsets ); 386 watchers.push(watcher);
387 watcher.update(); 388 return watcher; 389 }; 390 391 exports.update = function() { 392 latestEvent = null; 393 calculateViewport(); 394 updateAndTriggerWatchers(); 395 }; 396 exports.recalculateLocations = function() { 397 exports.documentHeight = 0; 398 exports.update(); 399 }; 400 401 return exports; 402});
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.