PageSourceSearch

https://letai.ru/bitrix/js/main/polyfill/intersectionobserver/js/intersectionobserver.js?163964074722411

js letai.ru collected 2026-09-24 09:59:51 UTC 22,411 bytes, 831 lines download raw bytes

1/**
2 * Copyright 2016 Google Inc. All Rights Reserved.
3 *
4 * Licensed under the W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE.
5 *
6 * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
7 *
8 */
9
10;(function(window, document) {
11	'use strict';
12
13	if ('IntersectionObserver' in window &&
14		'IntersectionObserverEntry' in window &&
15		'intersectionRatio' in window.IntersectionObserverEntry.prototype)
16	{
17
18		// Minimal polyfill for Edge 15's lack of `isIntersecting`
19		// See: https://github.com/w3c/IntersectionObserver/issues/211
20		if (!('isIntersecting' in window.IntersectionObserverEntry.prototype))
21		{
22			Object.defineProperty(window.IntersectionObserverEntry.prototype,
23				'isIntersecting', {
24					get: function ()
25					{
26						return this.intersectionRatio > 0;
27					}
28				});
29		}
30
31		return;
32	}
33
34
35	/**
36	 * An IntersectionObserver registry. This registry exists to hold a strong
37	 * reference to IntersectionObserver instances currently observering a target
38	 * element. Without this registry, instances without another reference may be
39	 * garbage collected.
40	 */
41	var registry = [];
42
43
44	/**
45	 * Creates the global IntersectionObserverEntry constructor.
46	 * https://w3c.github.io/IntersectionObserver/#intersection-observer-entry
47	 * @param {Object} entry A dictionary of instance properties.
48	 * @constructor
49	 */
50	function IntersectionObserverEntry(entry)
51	{
52		this.time = entry.time;
53		this.target = entry.target;
54		this.rootBounds = entry.rootBounds;
55		this.boundingClientRect = entry.boundingClientRect;
56		this.intersectionRect = entry.intersectionRect || getEmptyRect();
57		this.isIntersecting = !!entry.intersectionRect;
58
59		// Calculates the intersection ratio.
60		var targetRect = this.boundingClientRect;
61		var targetArea = targetRect.width * targetRect.height;
62		var intersectionRect = this.intersectionRect;
63		var intersectionArea = intersectionRect.width * intersectionRect.height;
64
65		// Sets intersection ratio.
66		if (targetArea)
67		{
68			this.intersectionRatio = intersectionArea / targetArea;
69		}
70		else
71		{
72			// If area is zero and is intersecting, sets to 1, otherwise to 0
73			this.intersectionRatio = this.isIntersecting ? 1 : 0;
74		}
75	}
76
77
78	/**
79	 * Creates the global IntersectionObserver constructor.
80	 * https://w3c.github.io/IntersectionObserver/#intersection-observer-interface
81	 * @param {Function} callback The function to be invoked after intersection
82	 *     changes have queued. The function is not invoked if the queue has
83	 *     been emptied by calling the `takeRecords` method.
84	 * @param {Object=} opt_options Optional configuration options.
85	 * @constructor
86	 */
87	function IntersectionObserver(callback, opt_options)
88	{
89		var options = opt_options || {};
90
91		if (typeof callback !== 'function')
92		{
93			throw new Error('callback must be a function');
94		}
95
96		if (options.root && options.root.nodeType !== 1)
97		{
98			throw new Error('root must be an Element');
99		}
100
101		// Binds and throttles `this._checkForIntersections`.
102		this._checkForIntersections = throttle(
103			this._checkForIntersections.bind(this), this.THROTTLE_TIMEOUT);
104
105		// Private properties.
106		this._callback = callback;
107		this._observationTargets = [];
108		this._queuedEntries = [];
109		this._rootMarginValues = this._parseRootMargin(options.rootMargin);
110
111		// Public properties.
112		this.thresholds = this._initThresholds(options.threshold);
113		this.root = options.root || null;
114		this.rootMargin = this._rootMarginValues.map(function(margin) {
115			return margin.value + margin.unit;
116		}).join(' ');
117	}
118
119
120	/**
121	 * The minimum interval within which the document will be checked for
122	 * intersection changes.
123	 */
124	IntersectionObserver.prototype.THROTTLE_TIMEOUT = 100;
125
126
127	/**
128	 * The frequency in which the polyfill polls for intersection changes.
129	 * this can be updated on a per instance basis and must be set prior to
130	 * calling `observe` on the first target.
131	 */
132	IntersectionObserver.prototype.POLL_INTERVAL = null;
133
134	/**
135	 * Use a mutation observer on the root element
136	 * to detect intersection changes.
137	 */
138	IntersectionObserver.prototype.USE_MUTATION_OBSERVER = true;
139
140
141	/**
142	 * Starts observing a target element for intersection changes based on
143	 * the thresholds values.
144	 * @param {Element} target The DOM element to observe.
145	 */
146	IntersectionObserver.prototype.observe = function(target)
147	{
148		var isTargetAlreadyObserved = this._observationTargets.some(function(item) {
149			return item.element === target;
150		});
151
152		if (isTargetAlreadyObserved)
153		{
154			return;
155		}
156
157		if (!(target && target.nodeType === 1))
158		{
159			throw new Error('target must be an Element');
160		}
161
162		this._registerInstance();
163		this._observationTargets.push({element: target, entry: null});
164		this._monitorIntersections();
165		this._checkForIntersections();
166	};
167
168
169	// noinspection JSUnusedGlobalSymbols
170	/**
171	 * Stops observing a target element for intersection changes.
172	 * @param {Element} target The DOM element to observe.
173	 */
174	IntersectionObserver.prototype.unobserve = function(target)
175	{
176		this._observationTargets = this._observationTargets.filter(function(item) {
177			return item.element !== target;
178		});
179
180		if (!this._observationTargets.length)
181		{
182			this._unmonitorIntersections();
183			this._unregisterInstance();
184		}
185	};
186
187
188	/**
189	 * Stops observing all target elements for intersection changes.
190	 */
191	IntersectionObserver.prototype.disconnect = function()
192	{
193		this._observationTargets = [];
194		this._unmonitorIntersections();
195		this._unregisterInstance();
196	};
197
198
199	/**
200	 * Returns any queue entries that have not yet been reported to the
201	 * callback and clears the queue. This can be used in conjunction with the
202	 * callback to obtain the absolute most up-to-date intersection information.
203	 * @return {Array} The currently queued entries.
204	 */
205	IntersectionObserver.prototype.takeRecords = function()
206	{
207		var records = this._queuedEntries.slice();
208		this._queuedEntries = [];
209		return records;
210	};
211
212
213	/**
214	 * Accepts the threshold value from the user configuration object and
215	 * returns a sorted array of unique threshold values. If a value is not
216	 * between 0 and 1 and error is thrown.
217	 * @private
218	 * @param {Array|number=} opt_threshold An optional threshold value or
219	 *     a list of threshold values, defaulting to [0].
220	 * @return {Array} A sorted list of unique and valid threshold values.
221	 */
222	IntersectionObserver.prototype._initThresholds = function(opt_threshold)
223	{
224		var threshold = opt_threshold || [0];
225
226		if (!Array.isArray(threshold))
227		{
228			threshold = [threshold];
229		}
230
231		return threshold.sort().filter(function(t, i, a) {
232			if (typeof t !== 'number' || isNaN(t) || t < 0 || t > 1)
233			{
234				throw new Error('threshold must be a number between 0 and 1 inclusively');
235			}
236			return t !== a[i - 1];
237		});
238	};
239
240
241	/**
242	 * Accepts the rootMargin value from the user configuration object
243	 * and returns an array of the four margin values as an object c
243ontaining
244	 * the value and unit properties. If any of the values are not properly
245	 * formatted or use a unit other than px or %, and error is thrown.
246	 * @private
247	 * @param {string=} opt_rootMargin An optional rootMargin value,
248	 *     defaulting to '0px'.
249	 * @return {Array<Object>} An array of margin objects with the keys
250	 *     value and unit.
251	 */
252	IntersectionObserver.prototype._parseRootMargin = function(opt_rootMargin) {
253		var marginString = opt_rootMargin || '0px';
254		var margins = marginString.split(/\s+/).map(function(margin) {
255			var parts = /^(-?\d*\.?\d+)(px|%)$/.exec(margin);
256			if (!parts)
257			{
258				throw new Error('rootMargin must be specified in pixels or percent');
259			}
260
261			return {value: parseFloat(parts[1]), unit: parts[2]};
262		});
263
264		// Handles shorthand.
265		margins[1] = margins[1] || margins[0];
266		margins[2] = margins[2] || margins[0];
267		margins[3] = margins[3] || margins[1];
268
269		return margins;
270	};
271
272
273	/**
274	 * Starts polling for intersection changes if the polling is not already
275	 * happening, and if the page's visibilty state is visible.
276	 * @private
277	 */
278	IntersectionObserver.prototype._monitorIntersections = function()
279	{
280		if (!this._monitoringIntersections)
281		{
282			this._monitoringIntersections = true;
283
284			// If a poll interval is set, use polling instead of listening to
285			// resize and scroll events or DOM mutations.
286			if (this.POLL_INTERVAL)
287			{
288				this._monitoringInterval = setInterval(
289					this._checkForIntersections, this.POLL_INTERVAL);
290			}
291			else
292			{
293				addEvent(window, 'resize', this._checkForIntersections, true);
294				addEvent(document, 'scroll', this._checkForIntersections, true);
295
296				if (this.USE_MUTATION_OBSERVER && 'MutationObserver' in window)
297				{
298					this._domObserver = new MutationObserver(this._checkForIntersections);
299					this._domObserver.observe(document, {
300						attributes: true,
301						childList: true,
302						characterData: true,
303						subtree: true
304					});
305				}
306			}
307		}
308	};
309
310
311	/**
312	 * Stops polling for intersection changes.
313	 * @private
314	 */
315	IntersectionObserver.prototype._unmonitorIntersections = function()
316	{
317		if (this._monitoringIntersections)
318		{
319			this._monitoringIntersections = false;
320
321			clearInterval(this._monitoringInterval);
322			this._monitoringInterval = null;
323
324			removeEvent(window, 'resize', this._checkForIntersections, true);
325			removeEvent(document, 'scroll', this._checkForIntersections, true);
326
327			if (this._domObserver)
328			{
329				this._domObserver.disconnect();
330				this._domObserver = null;
331			}
332		}
333	};
334
335
336	/**
337	 * Scans each observation target for intersection changes and adds them
338	 * to the internal entries queue. If new entries are found, it
339	 * schedules the callback to be invoked.
340	 * @private
341	 */
342	IntersectionObserver.prototype._checkForIntersections = function()
343	{
344		var rootIsInDom = this._rootIsInDom();
345		var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect();
346
347		this._observationTargets.forEach(function(item) {
348			var target = item.element;
349			var targetRect = getBoundingClientRect(target);
350			var rootContainsTarget = this._rootContainsTarget(target);
351			var oldEntry = item.entry;
352			var intersectionRect = rootIsInDom && rootContainsTarget &&
353				this._computeTargetAndRootIntersection(target, rootRect);
354
355			var newEntry = item.entry = new IntersectionObserverEntry({
356				time: now(),
357				target: target,
358				boundingClientRect: targetRect,
359				rootBounds: rootRect,
360				intersectionRect: intersectionRect
361			});
362
363			if (!oldEntry)
364			{
365				this._queuedEntries.push(newEntry);
366			}
367			else if (rootIsInDom && rootContainsTarget)
368			{
369				// If the new entry intersection ratio has crossed any of the
370				// thresholds, add a new entry.
371				if (this._hasCrossedThreshold(oldEntry, newEntry))
372				{
373					this._queuedEntries.push(newEntry);
374				}
375			}
376			else
377			{
378				// If the root is not in the DOM or target is not contained within
379				// root but the previous entry for this target had an intersection,
380				// add a new record indicating removal.
381				if (oldEntry && oldEntry.isIntersecting)
382				{
383					this._queuedEntries.push(newEntry);
384				}
385			}
386		}, this);
387
388		if (this._queuedEntries.length)
389		{
390			this._callback(this.takeRecords(), this);
391		}
392	};
393
394
395	/**
396	 * Accepts a target and root rect computes the intersection between then
397	 * following the algorithm in the spec.
398	 * TODO(philipwalton): at this time clip-path is not considered.
399	 * https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo
400	 * @param {Element} target The target DOM element
401	 * @param {Object} rootRect The bounding rect of the root after being
402	 *     expanded by the rootMargin value.
403	 * @return {?Object} The final intersection rect object or undefined if no
404	 *     intersection is found.
405	 * @private
406	 */
407	IntersectionObserver.prototype._computeTargetAndRootIntersection = function(target, rootRect)
408	{
409		// If the element isn't displayed, an intersection can't happen.
410		if (window.getComputedStyle(target).display === 'none')
411		{
412			return;
413		}
414
415		var intersectionRect = getBoundingClientRect(target);
416		var parent = getParentNode(target);
417		var atRoot = false;
418
419		while (!atRoot)
420		{
421			var parentRect = null;
422			var parentComputedStyle = parent.nodeType === 1 ?
423				window.getComputedStyle(parent) : {};
424
425			// If the parent isn't displayed, an intersection can't happen.
426			// noinspection EqualityComparisonWithCoercionJS
427			if (parentComputedStyle.display == 'none')
428			{
429				return;
430			}
431
432			if (parent === this.root || parent === document)
433			{
434				atRoot = true;
435				parentRect = rootRect;
436			}
437			else
438			{
439				// If the element has a non-visible overflow, and it's not the <body>
440				// or <html> element, update the intersection rect.
441				// Note: <body> and <html> cannot be clipped to a rect that's not also
442				// the document rect, so no need to compute a new intersection.
443				// noinspection EqualityComparisonWithCoercionJS
444				if (parent != document.body &&
445					parent != document.documentElement &&
446					parentComputedStyle.overflow != 'visible')
447				{
448					parentRect = getBoundingClientRect(parent);
449				}
450			}
451
452			// If either of the above conditionals set a new parentRect,
453			// calculate new intersection data.
454			if (parentRect)
455			{
456				intersectionRect = computeRectIntersection(parentRect, intersectionRect);
457
458				if (!intersectionRect)
459				{
460					break;
461				}
462			}
463
464			parent = getParentNode(parent);
465		}
466
467		return intersectionRect;
468	};
469
470
471	/**
472	 * Returns the root rect after being expanded by the rootMargin value.
473	 * @return {Object} The expanded root rect.
474	 * @private
475	 */
476	IntersectionObserver.prototype._getRootRect = function()
477	{
478		var rootRect;
479
480		if (this.root)
481		{
482			rootRect = getBoundingClientRect(this.root);
483		}
484		else
485		{
486			// Use <html>/<body> instead of window since scroll bars affect size.
487			var html = document.documentElement;
488			var body = document.body;
489			rootRect = {
490				top: 0,
491				left: 0,
492				right: html.clientWidth || body.clientWidth,
493				width: html.clientWidth || body.clientWidth,
494				bottom: html.clientHeight || body.clientHeight,
495				height: html.clientHeight || body.clientHeight
496			};
497		}
498
499		return this._expandRectByRootMargin(rootRect);
500	};
501
502
503	/**
504	 * Accepts a rect and expands it by the rootMargin value.
505	 * @param {Object} rect The rect object to expand.
506	 * @return {Object} The expanded rect.
507	 * @private
508	 */
509	IntersectionObserver.prototype._expandRectByRootMargin = function(rect)
510	{
511		var margins = this._rootMarginValues.map(function(margin, i) {
512			return margin.unit === 'px' ? margin.value :
513				margin.value * (i % 2 ? rect.width : rect.height) / 100;
514		});
515		var newRect = {
516			top: rect.top - margins[0],
517			right: rect.right + margins[1],
518			bottom: rect.bottom + margins[2],
519			left: rect.left - margins[3]
520		};
521		newRect.width = newRect.right - newRect.left;
522		newRect.height = newRect.bottom - newRect.top;
523
524		return newRect;
525	};
526
527
528	/**
529	 * Accepts an old and new entry and returns true if at least one of the
530	 * threshold values has been crossed.
531	 * @param {?IntersectionObserverEntry} oldEntry The previous entry for a
532	 *    particular target element or null if no previous entry exists.
533	 * @param {IntersectionObserverEntry} newEntry The current entry for a
534	 *    particular target element.
535	 * @return {boolean} Returns true if a any threshold has been crossed.
536	 * @private
537	 */
538	IntersectionObserver.prototype._hasCrossedThreshold = function(oldEntry, newEntry)
539	{
540		// To make comparing easier, an entry that has a ratio of 0
541		// but does not actually intersect is given a value of -1
542		var oldRatio = oldEntry && oldEntry.isIntersecting ?
543			oldEntry.intersectionRatio || 0 : -1;
544		var newRatio = newEntry.isIntersecting ?
545			newEntry.intersectionRatio || 0 : -1;
546
547		// Ignore unchanged ratios
548		if (oldRatio === newRatio)
549		{
550			return;
551		}
552
553		for (var i = 0; i < this.thresholds.length; i++)
554		{
555			var threshold = this.thresholds[i];
556
557			// Return true if an entry matches a threshold or if the new ratio
558			// and the old ratio are on the opposite sides of a threshold.
559			// noinspection EqualityComparisonWithCoercionJS
560			if (threshold == oldRatio || threshold == newRatio ||
561				threshold < oldRatio !== threshold < newRatio)
562			{
563				return true;
564			}
565		}
566	};
567
568
569	/**
570	 * Returns whether or not the root element is an element and is in the DOM.
571	 * @return {boolean} True if the root element is an element and is in the DOM.
572	 * @private
573	 */
574	IntersectionObserver.prototype._rootIsInDom = function()
575	{
576		return !this.root || containsDeep(document, this.root);
577	};
578
579
580	/**
581	 * Returns whether or not the target element is a child of root.
582	 * @param {Element} target The target element to check.
583	 * @return {boolean} True if the target element is a child of root.
584	 * @private
585	 */
586	IntersectionObserver.prototype._rootContainsTarget = function(target)
587	{
588		return containsDeep(this.root || document, target);
589	};
590
591
592	/**
593	 * Adds the instance to the global IntersectionObserver registry if it isn't
594	 * already present.
595	 * @private
596	 */
597	IntersectionObserver.prototype._registerInstance = function()
598	{
599		if (registry.indexOf(this) < 0)
600		{
601			registry.push(this);
602		}
603	};
604
605
606	/**
607	 * Removes the instance from the global IntersectionObserver registry.
608	 * @private
609	 */
610	IntersectionObserver.prototype._unregisterInstance = function()
611	{
612		var index = registry.indexOf(this);
613
614		if (index !== -1)
615		{
616			registry.splice(index, 1);
617		}
618	};
619
620
621	/**
622	 * Returns the result of the performance.now() method or null in browsers
623	 * that don't support the API.
624	 * @return {number} The elapsed time since the page was requested.
625	 */
626	function now()
627	{
628		return window.performance && performance.now && performance.now();
629	}
630
631
632	/**
633	 * Throttles a function and delays its executiong, so it's only called at most
634	 * once within a given time period.
635	 * @param {Function} fn The function to throttle.
636	 * @param {number} timeout The amount of time that must pass before the
637	 *     function can be called again.
638	 * @return {Function} The throttled function.
639	 */
640	function throttle(fn, timeout)
641	{
642		var timer = null;
643		return function () {
644			if (!timer) {
645				timer = setTimeout(function() {
646					fn();
647					timer = null;
648				}, timeout);
649			}
650		};
651	}
652
653
654	/**
655	 * Adds an event handler to a DOM node ensuring cross-browser compatibility.
656	 * @param {Node|Window|HTMLDocument} node The DOM node to add the event handler to.
657	 * @param {string} event The event name.
658	 * @param {Function} fn The event handler to add.
659	 * @param {boolean} opt_useCapture Optionally adds the even to the capture
660	 *     phase. Note: this only works in modern browsers.
661	 */
662	function addEvent(node, event, fn, opt_useCapture)
663	{
664		// noinspection EqualityComparisonWithCoercionJS
665		if (typeof node.addEventListener == 'function')
666		{
667			node.addEventListener(event, fn, opt_useCapture || false);
668		}
669		else if (typeof node.attachEvent === 'function')
670		{
671			node.attachEvent('on' + event, fn);
672		}
673	}
674
675
676	/**
677	 * Removes a previously added event handler from a DOM node.
678	 * @param {Node|Window|Document} node The DOM node to remove the event handler from.
679	 * @param {string} event The event name.
680	 * @param {Function} fn The event handler to remove.
681	 * @param {boolean} opt_useCapture If the event handler was added with this
682	 *     flag set to true, it should be set to true here in order to remove it.
683	 */
684	function removeEvent(node, event, fn, opt_useCapture)
685	{
686		if (typeof node.removeEventListener === 'function')
687		{
688			node.removeEventListener(event, fn, opt_useCapture || false);
689		}
690		else if (typeof node.detatchEvent === 'function')
691		{
692			node.detatchEvent('on' + event, fn);
693		}
694	}
695
696
697	/**
698	 * Returns the intersection between two rect objects.
699	 * @param {Object} rect1 The first rect.
700	 * @param {Object} rect2 The second rect.
701	 * @return {?Object} The intersection rect or undefined if no intersection
702	 *     is found.
703	 */
704	function computeRectIntersection(rect1, rect2)
705	{
706		var top = Math.max(rect1.top, rect2.top);
707		var bottom = Math.min(rect1.bottom, rect2.bottom);
708		var left = Math.max(rect1.left, rect2.left);
709		var right = Math.min(rect1.right, rect2.right);
710		var width = right - left;
711		var height = bottom - top;
712
713		return (width >= 0 && height >= 0) && {
714			top: top,
715			bottom: bottom,
716			left: left,
717			right: right,
718			width: width,
719			height: height
720		};
721	}
722
723
724	/**
725	 * Shims the native getBoundingClientRect for compatibility with older IE.
726	 * @param {Element} el The element whose bounding rect to get.
727	 * @return {Object} The (possibly shimmed) rect of the element.
728	 */
729	function getBoundingClientRect(el)
730	{
731		var rect;
732
733		try
734		{
735			rect = el.getBoundingClientRect();
736		}
737		catch (err)
738		{
739			// Ignore Windows 7 IE11 "Unspecified error"
740			// https://github.com/w3c/IntersectionObserver/pull/205
741		}
742
743		if (!rect)
744		{
745			return getEmptyRect();
746		}
747
748		// Older IE
749		if (!(rect.width && rect.height))
750		{
751			rect = {
752				top: rect.top,
753				right: rect.right,
754				bottom: rect.bottom,
755				left: rect.left,
756				width: rect.right - rect.left,
757				height: rect.bottom - rect.top
758			};
759		}
760
761		return rect;
762	}
763
764
765	/**
766	 * Returns an empty rect object. An empty rect is returned when an element
767	 * is not in the DOM.
768	 * @return {Object} The empty rect.
769	 */
770	function getEmptyRect()
771	{
772		return {
773			top: 0,
774			bottom: 0,
775			left: 0,
776			right: 0,
777			width: 0,
778			height: 0
779		};
780	}
781
782	/**
783	 * Checks to see if a parent element contains a child element (including inside
784	 * shadow DOM).
785	 * @param {Node} parent The parent element.
786	 * @param {Node} child The child element.
787	 * @return {boolean} True if the parent node contains the child node.
788	 */
789	function containsDeep(parent, child)
790	{
791		var node = child;
792
793		while (node)
794		{
795			if (node === parent)
796			{
797				return true;
798			}
799
800			node = getParentNode(node);
801		}
802
803		return false;
804	}
805
806
807	/**
808	 * Gets the parent node of an element or its host element if the parent node
809	 * is a shadow root.
810	 * @param {Node} node The node whose parent to get.
811	 * @return {Node|null} The parent node or null if no parent exists.
812	 */
813	function getParentNode(node)
814	{
815		var parent = node.parentNode;
816
817		// noinspection EqualityComparisonWithCoercionJS
818		if (parent && parent.nodeType == 11 && parent.host)
819		{
820			// If the parent is a shadow root, return the host element.
821			return parent.host;
822		}
823
824		return parent;
825	}
826
827	// Exposes the constructors globally.
828	window.IntersectionObserver = IntersectionObserver;
829	window.IntersectionObserverEntry = IntersectionObserverEntry;
830
831}(window, document));

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.