足力健前端,vue版本

intersection-observer.js 22KB

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