PageSourceSearch

https://www.arttowermito.or.jp/js/intersection-observer.js

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

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.