PageSourceSearch

https://forms.tay.ca/_Template/Get/2ef3c20d-0c62-43ad-adf9-af85007…/intersection-observer.js?cdv=1211473571

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

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.