PageSourceSearch

https://www.hologic.es/sites/default/files/cohesion/scripts/inters…/intersection-observer.js?tm4wcr

js hologic.es collected 2026-10-08 20:32:08 UTC 24,080 bytes, 727 lines download raw bytes

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