PageSourceSearch

http://www.wearethepractitioners.com/packages/c5box_framework/themes/framework/js/scrollMonitor.js

js wearethepractitioners.com collected 2026-09-25 01:21:47 UTC 10,931 bytes, 375 lines download raw bytes

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		while( updateAndTriggerWatchersI-- ) {
94			watchers[updateAndTriggerWatchersI].update();
95		}
96
97		updateAndTriggerWatchersI = watchers.length;
98		while( updateAndTriggerWatchersI-- ) {
99			watchers[updateAndTriggerWatchersI].triggerCallbacks();
100		}
101
102	}
103
104	function ElementWatcher( watchItem, offsets ) {
105		var self = this;
106
107		this.watchItem = watchItem;
108
109		if (!offsets) {
110			this.offsets = defaultOffsets;
111		} else if (offsets === +offsets) {
112			this.offsets = {top: offsets, bottom: offsets};
113		} else {
114			this.offsets = {
115				top: offsets.top || defaultOffsets.top,
116				bottom: offsets.bottom || defaultOffsets.bottom
117			};
118		}
119
120		this.callbacks = {}; // {callback: function, isOne: true }
121
122		for (var i = 0, j = eventTypes.length; i < j; i++) {
123			self.callbacks[eventTypes[i]] = [];
124		}
125
126		this.locked = false;
127
128		var wasInViewport;
129		var wasFullyInViewport;
130		var wasAboveViewport;
131		var wasBelowViewport;
132
133		var listenerToTriggerListI;
134		var listener;
135		function triggerCallbackArray( listeners ) {
136			if (listeners.length === 0) {
137				return;
138			}
139			listenerToTriggerListI = listeners.length;
140			while( listenerToTriggerListI-- ) {
141				listener = listeners[listenerToTriggerListI];
142				listener.callback.call( self, latestEvent );
143				if (listener.isOne) {
144					listeners.splice(listenerToTriggerListI, 1);
145				}
146			}
147		}
148		this.triggerCallbacks = function triggerCallbacks() {
149
150			if (this.isInViewport && !wasInViewport) {
151				triggerCallbackArray( this.callbacks[ENTERVIEWPORT] );
152			}
153			if (this.isFullyInViewport && !wasFullyInViewport) {
154				triggerCallbackArray( this.callbacks[FULLYENTERVIEWPORT] );
155			}
156
157
158			if (this.isAboveViewport !== wasAboveViewport &&
159				this.isBelowViewport !== wasBelowViewport) {
160
161				triggerCallbackArray( this.callbacks[VISIBILITYCHANGE] );
162
163				// if you skip completely past this element
164				if (!wasFullyInViewport && !this.isFullyInViewport) {
165					triggerCallbackArray( this.callbacks[FULLYENTERVIEWPORT] );
166					triggerCallbackArray( this.callbacks[PARTIALLYEXITVIEWPORT] );
167				}
168				if (!wasInViewport && !this.isInViewport) {
169					triggerCallbackArray( this.callbacks[ENTERVIEWPORT] );
170					triggerCallbackArray( this.callbacks[EXITVIEWPORT] );
171				}
172			}
173
174			if (!this.isFullyInViewport && wasFullyInViewport) {
175				triggerCallbackArray( this.callbacks[PARTIALLYEXITVIEWPORT] );
176			}
177			if (!this.isInViewport && wasInViewport) {
178				triggerCallbackArray( this.callbacks[EXITVIEWPORT] );
179			}
180			if (this.isInViewport !== wasInViewport) {
181				triggerCallbackArray( this.callbacks[VISIBILITYCHANGE] );
182			}
183			switch( true ) {
184				case wasInViewport !== this.isInViewport:
185				case wasFullyInViewport !== this.isFullyInViewport:
186				case wasAboveViewport !== this.isAboveViewport:
187				case wasBelowViewport !== this.isBelowViewport:
188					triggerCallbackArray( this.callbacks[STATECHANGE] );
189			}
190
191			wasInViewport = this.isInViewport;
192			wasFullyInViewport = this.isFullyInViewport;
193			wasAboveViewport = this.isAboveViewport;
194			wasBelowViewport = this.isBelowViewport;
195
196		};
197
198		this.recalculateLocation = function() {
199			if (this.locked) {
200				return;
201			}
202			var previousTop = this.top;
203			var previousBottom = this.bottom;
204			if (this.watchItem.nodeName) { // a dom element
205				var cachedDisplay = this.watchItem.style.display;
206				if (cachedDisplay === 'none') {
207					this.watchItem.style.display = '';
208				}
209
210				var boundingRect = this.watchItem.getBoundingClientRect();
211				this.top = boundingRect.top + exports.viewportTop;
212				this.bottom = boundingRect.bottom + exports.viewportTop;
213
214				if (cachedDisplay === 'none') {
215					this.watchItem.style.display = cachedDisplay;
216				}
217
218			} else if (this.watchItem === +this.watchItem) { // number
219				if (this.watchItem > 0) {
220					this.top = this.bottom = this.watchItem;
221				} else {
222					this.top = this.bottom = exports.documentHeight - this.watchItem;
223				}
224
225			} else { // an object with a top and bottom property
226				this.top = this.watchItem.top;
227				this.bottom = this.watchItem.bottom;
228			}
229
230			this.top -= this.offsets.top;
231			this.bottom += this.offsets.bottom;
232			this.height = this.bottom - this.top;
233
234			if ( (previousTop !== undefined || previousBottom !== undefined) && (this.top !== previousTop || this.bottom !== previousBottom) ) {
235				triggerCallbackArray( this.callbacks[LOCATIONCHANGE] );
236			}
237		};
238
239		this.recalculateLocation();
240		this.update();
241
242		wasInViewport = this.isInViewport;
243		wasFullyInViewport = this.isFullyInViewport;
244		wasAboveViewport = this.isAboveViewport;
245		wasBelowViewport = this.isBelowViewport;
246	}
247
248	ElementWatcher.prototype = {
249		on: function( event, callback, isOne ) {
250
251			// trigger the event if it applies to the element right now.
252			switch( true ) {
253				case event === VISIBILITYCHANGE && !this.isInViewport && this.isAboveViewport:
254				case event === ENTERVIEWPORT && this.isInViewport:
255				case event === FULLYENTERVIEWPORT && this.isFullyInViewport:
256				case event === EXITVIEWPORT && this.isAboveViewport && !this.isInViewport:
257				case event === PARTIALLYEXITVIEWPORT && this.isAboveViewport:
258					callback.call( this, latestEvent );
259					if (isOne) {
260						return;
261					}
262			}
263
264			if (this.callbacks[event]) {
265				this.callbacks[event].push({callback: callback, isOne: isOne||false});
266			} else {
267				throw new Error('Tried to add a scroll monitor listener of type '+event+'. Your options are: '+eventTypes.join(', '));
268			}
269		},
270		off: function( event, callback ) {
271			if (this.callbacks[event]) {
272				for (var i = 0, item; item = this.callbacks[event][i]; i++) {
273					if (item.callback === callback) {
274						this.callbacks[event].splice(i, 1);
275						break;
276					}
277				}
278			} else {
279				throw new Error('Tried to remove a scroll monitor listener of type '+event+'. Your options are: '+eventTypes.join(', '));
280			}
281		},
282		one: function( event, callback ) {
283			this.on( event, callback, true);
284		},
285		recalculateSize: function() {
286			this.height = this.watchItem.offsetHeight + this.offsets.top + this.offsets.bottom;
287			this.bottom = this.top + this.height;
288		},
289		update: function() {
290			this.isAboveViewport = this.top < exports.viewportTop;
291			this.isBelowViewport = this.bottom > exports.viewportBottom;
292
293			this.isInViewport = (this.top <= exports.viewportBottom && this.bottom >= exports.viewportTop);
294			this.isFullyInViewport = (this.top >= exports.viewportTop && this.bottom <= exports.viewportBottom) ||
295								 (this.isAboveViewport && this.isBelowViewport);
296
297		},
298		destroy: function() {
299			var index = watchers.indexOf(this),
300				self  = this;
301			watchers.splice(index, 1);
302			for (var i = 0, j = eventTypes.length; i < j; i++) {
303				self.callbacks[eventTypes[i]].length = 0;
304			}
305		},
306		// prevent recalculating the element location
307		lock: function() {
308			this.locked = true;
309		},
310		unlock: function() {
311			this.locked = false;
312		}
313	};
314
315	var eventHandlerFactory = function (type) {
316		return function( callback, isOne ) {
317			this.on.call(this, type, callback, isOne);
318		};
319	};
320
321	for (var i = 0, j = eventTypes.length; i < j; i++) {
322		var type =  eventTypes[i];
323		ElementWatcher.prototype[type] = eventHandlerFactory(type);
324	}
325
326	try {
327		calculateViewport();
328	} catch (e) {
329		try {
330			window.$(calculateViewport);
331		} catch (e) {
332			throw new Error('If you must put scrollMonitor in the <head>, you must use jQuery.');
333		}
334	}
335
336	function scrollMonitorListener(event) {
337		latestEvent = event;
338		calculateViewport();
339		updateAndTriggerWatchers();
340	}
341
342	if (window.addEventListener) {
343		window.addEventListener('scroll', scrollMonitorListener);
344		window.addEventListener('resize', debouncedRecalcuateAndTrigger);
345	} else {
346		// Old IE support
347		window.attachEvent('onscroll', scrollMonitorListener);
348		window.attachEvent('onresize', debouncedRecalcuateAndTrigger);
349	}
350
351	exports.beget = exports.create = function( element, offsets ) {
352		if (typeof element === 'string') {
353			element = document.querySelector(element);
354		} else if (element && element.length > 0) {
355			element = element[0];
356		}
357
358		var watcher = new ElementWatcher( element, offsets );
359		watchers.push(watcher);
360		watcher.update();
361		return watcher;
362	};
363
364	exports.update = function() {
365		latestEvent = null;
366		calculateViewport();
367		updateAndTriggerWatchers();
368	};
369	exports.recalculateLocations = function() {
370		exports.documentHeight = 0;
371		exports.update();
372	};
373
374	return exports;
375});

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.