PageSourceSearch

https://www.corrienteshoy.com/js/polyfill-intersection-observer.js

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