vendor: 4,179 bytes, lines 1-135
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 * Returns the embedding frame element, if any. 38 * @param {!Document} doc 39 * @return {!Element} 40 */ 41function getFrameElement(doc) { 42 try { 43 return doc.defaultView && doc.defaultView.frameElement || null; 44 } catch (e) { 45 // Ignore the error. 46 return null; 47 } 48} 49 50/** 51 * A local reference to the root document. 52 */ 53var document = (function(startDoc) { 54 var doc = startDoc; 55 var frame = getFrameElement(doc); 56 while (frame) { 57 doc = frame.ownerDocument; 58 frame = getFrameElement(doc); 59 } 60 return doc; 61})(window.document); 62 63/** 64 * An IntersectionObserver registry. This registry exists to hold a strong 65 * reference to IntersectionObserver instances currently observing a target 66 * element. Without this registry, instances without another reference may be 67 * garbage collected. 68 */ 69var registry = []; 70 71/** 72 * The signal updater for cross-origin intersection. When not null, it means 73 * that the polyfill is configured to work in a cross-origin mode. 74 * @type {function(DOMRect|ClientRect, DOMRect|ClientRect)} 75 */ 76var crossOriginUpdater = null; 77 78/** 79 * The current cross-origin intersection. Only used in the cross-origin mode. 80 * @type {DOMRect|ClientRect} 81 */ 82var crossOriginRect = null; 83 84 85/** 86 * Creates the global IntersectionObserverEntry constructor. 87 * https://w3c.github.io/IntersectionObserver/#intersection-observer-entry 88 * @param {Object} entry A dictionary of instance properties. 89 * @constructor 90 */ 91function IntersectionObserverEntry(entry) { 92 this.time = entry.time; 93 this.target = entry.target; 94 this.rootBounds = ensureDOMRect(entry.rootBounds); 95 this.boundingClientRect = ensureDOMRect(entry.boundingClientRect); 96 this.intersectionRect = ensureDOMRect(entry.intersectionRect || getEmptyRect()); 97 this.isIntersecting = !!entry.intersectionRect; 98 99 // Calculates the intersection ratio. 100 var targetRect = this.boundingClientRect; 101 var targetArea = targetRect.width * targetRect.height; 102 var intersectionRect = this.intersectionRect; 103 var intersectionArea = intersectionRect.width * intersectionRect.height; 104 105 // Sets intersection ratio. 106 if (targetArea) { 107 // Round the intersection ratio to avoid floating point math issues: 108 // https://github.com/w3c/IntersectionObserver/issues/324 109 this.intersectionRatio = Number((intersectionArea / targetArea).toFixed(4)); 110 } else { 111 // If area is zero and is intersecting, sets to 1, otherwise to 0 112 this.intersectionRatio = this.isIntersecting ? 1 : 0; 113 } 114} 115 116 117/** 118 * Creates the global IntersectionObserver constructor. 119 * https://w3c.github.io/IntersectionObserver/#intersection-observer-interface 120 * @param {Function} callback The function to be invoked after intersection 121 * changes have queued. The function is not invoked if the queue has 122 * been emptied by calling the `takeRecords` method. 123 * @param {Object=} opt_options Optional configuration options. 124 * @constructor 125 */ 126function IntersectionObserver(callback, opt_options) { 127 128 var options = opt_options || {}; 129 130 if (typeof callback != 'function') { 131 throw new Error('callback must be a function'); 132 } 133 134 if (options.root && options.root.nodeType != 1) { 135 throw new Error('root must be an
vendor: 7,967 bytes, lines 135-398
135 Element'); 136 } 137 138 // Binds and throttles `this._checkForIntersections`. 139 this._checkForIntersections = throttle( 140 this._checkForIntersections.bind(this), this.THROTTLE_TIMEOUT); 141 142 // Private properties. 143 this._callback = callback; 144 this._observationTargets = []; 145 this._queuedEntries = []; 146 this._rootMarginValues = this._parseRootMargin(options.rootMargin); 147 148 // Public properties. 149 this.thresholds = this._initThresholds(options.threshold); 150 this.root = options.root || null; 151 this.rootMargin = this._rootMarginValues.map(function(margin) { 152 return margin.value + margin.unit; 153 }).join(' '); 154 155 /** @private @const {!Array<!Document>} */ 156 this._monitoringDocuments = []; 157 /** @private @const {!Array<function()>} */ 158 this._monitoringUnsubscribes = []; 159} 160 161 162/** 163 * The minimum interval within which the document will be checked for 164 * intersection changes. 165 */ 166IntersectionObserver.prototype.THROTTLE_TIMEOUT = 100; 167 168 169/** 170 * The frequency in which the polyfill polls for intersection changes. 171 * this can be updated on a per instance basis and must be set prior to 172 * calling `observe` on the first target. 173 */ 174IntersectionObserver.prototype.POLL_INTERVAL = null; 175 176/** 177 * Use a mutation observer on the root element 178 * to detect intersection changes. 179 */ 180IntersectionObserver.prototype.USE_MUTATION_OBSERVER = true; 181 182 183/** 184 * Sets up the polyfill in the cross-origin mode. The result is the 185 * updater function that accepts two arguments: `boundingClientRect` and 186 * `intersectionRect` - just as these fields would be available to the 187 * parent via `IntersectionObserverEntry`. This function should be called 188 * each time the iframe receives intersection information from the parent 189 * window, e.g. via messaging. 190 * @return {function(DOMRect|ClientRect, DOMRect|ClientRect)} 191 */ 192IntersectionObserver._setupCrossOriginUpdater = function() { 193 if (!crossOriginUpdater) { 194 /** 195 * @param {DOMRect|ClientRect} boundingClientRect 196 * @param {DOMRect|ClientRect} intersectionRect 197 */ 198 crossOriginUpdater = function(boundingClientRect, intersectionRect) { 199 if (!boundingClientRect || !intersectionRect) { 200 crossOriginRect = getEmptyRect(); 201 } else { 202 crossOriginRect = convertFromParentRect(boundingClientRect, intersectionRect); 203 } 204 registry.forEach(function(observer) { 205 observer._checkForIntersections(); 206 }); 207 }; 208 } 209 return crossOriginUpdater; 210}; 211 212 213/** 214 * Resets the cross-origin mode. 215 */ 216IntersectionObserver._resetCrossOriginUpdater = function() { 217 crossOriginUpdater = null; 218 crossOriginRect = null; 219}; 220 221 222/** 223 * Starts observing a target element for intersection changes based on 224 * the thresholds values. 225 * @param {Element} target The DOM element to observe. 226 */ 227IntersectionObserver.prototype.observe = function(target) { 228 var isTargetAlreadyObserved = this._observationTargets.some(function(item) { 229 return item.element == target; 230 }); 231 232 if (isTargetAlreadyObserved) { 233 return; 234 } 235 236 if (!(target && target.nodeType == 1)) { 237 throw new Error('target must be an Element'); 238 } 239 240 this._registerInstance(); 241 this._observationTargets.push({element: target, entry: null}); 242 this._monitorIntersections(target.ownerDocument); 243 this._checkForIntersections(); 244}; 245 246 247/** 248 * Stops observing a target element for intersection changes. 249 * @param {Element} target The DOM element to observe. 250 */ 251IntersectionObserver.prototype.unobserve = function(target) { 252 this._observationTargets = 253 this._observationTargets.filter(function(item) { 254 return item.element != target; 255 }); 256 this._unmonitorIntersections(target.ownerDocument); 257 if (this._observationTargets.length == 0) { 258 this._unregisterInstance(); 259 } 260}; 261 262 263/** 264 * Stops observing all target elements for intersection changes. 265 */ 266IntersectionObserver.prototype.disconnect = function() { 267 this._observationTargets = []; 268 this._unmonitorAllIntersections(); 269 this._unregisterInstance(); 270}; 271 272 273/** 274 * Returns any queue entries that have not yet been reported to the 275 * callback and clears the queue. This can be used in conjunction with the 276 * callback to obtain the absolute most up-to-date intersection information. 277 * @return {Array} The currently queued entries. 278 */ 279IntersectionObserver.prototype.takeRecords = function() { 280 var records = this._queuedEntries.slice(); 281 this._queuedEntries = []; 282 return records; 283}; 284 285 286/** 287 * Accepts the threshold value from the user configuration object and 288 * returns a sorted array of unique threshold values. If a value is not 289 * between 0 and 1 and error is thrown. 290 * @private 291 * @param {Array|number=} opt_threshold An optional threshold value or 292 * a list of threshold values, defaulting to [0]. 293 * @return {Array} A sorted list of unique and valid threshold values. 294 */ 295IntersectionObserver.prototype._initThresholds = function(opt_threshold) { 296 var threshold = opt_threshold || [0]; 297 if (!Array.isArray(threshold)) threshold = [threshold]; 298 299 return threshold.sort().filter(function(t, i, a) { 300 if (typeof t != 'number' || isNaN(t) || t < 0 || t > 1) { 301 throw new Error('threshold must be a number between 0 and 1 inclusively'); 302 } 303 return t !== a[i - 1]; 304 }); 305}; 306 307 308/** 309 * Accepts the rootMargin value from the user configuration object 310 * and returns an array of the four margin values as an object containing 311 * the value and unit properties. If any of the values are not properly 312 * formatted or use a unit other than px or %, and error is thrown. 313 * @private 314 * @param {string=} opt_rootMargin An optional rootMargin value, 315 * defaulting to '0px'. 316 * @return {Array<Object>} An array of margin objects with the keys 317 * value and unit. 318 */ 319IntersectionObserver.prototype._parseRootMargin = function(opt_rootMargin) { 320 var marginString = opt_rootMargin || '0px'; 321 var margins = marginString.split(/\s+/).map(function(margin) { 322 var parts = /^(-?\d*\.?\d+)(px|%)$/.exec(margin); 323 if (!parts) { 324 throw new Error('rootMargin must be specified in pixels or percent'); 325 } 326 return {value: parseFloat(parts[1]), unit: parts[2]}; 327 }); 328 329 // Handles shorthand. 330 margins[1] = margins[1] || margins[0]; 331 margins[2] = margins[2] || margins[0]; 332 margins[3] = margins[3] || margins[1]; 333 334 return margins; 335}; 336 337 338/** 339 * Starts polling for intersection changes if the polling is not already 340 * happening, and if the page's visibility state is visible. 341 * @param {!Document} doc 342 * @private 343 */ 344IntersectionObserver.prototype._monitorIntersections = function(doc) { 345 var win = doc.defaultView; 346 if (!win) { 347 // Already destroyed. 348 return; 349 } 350 if (this._monitoringDocuments.indexOf(doc) != -1) { 351 // Already monitoring. 352 return; 353 } 354 355 // Private state for monitoring. 356 var callback = this._checkForIntersections; 357 var monitoringInterval = null; 358 var domObserver = null; 359 360 // If a poll interval is set, use polling instead of listening to 361 // resize and scroll events or DOM mutations. 362 if (this.POLL_INTERVAL) { 363 monitoringInterval = win.setInterval(callback, this.POLL_INTERVAL); 364 } else { 365 addEvent(win, 'resize', callback, true); 366 addEvent(doc, 'scroll', callback, true); 367 if (this.USE_MUTATION_OBSERVER && 'MutationObserver' in win) { 368 domObserver = new win.MutationObserver(callback); 369 domObserver.observe(doc, { 370 attributes: true, 371 childList: true, 372 characterData: true, 373 subtree: true 374 }); 375 } 376 } 377 378 this._monitoringDocuments.push(doc); 379 this._monitoringUnsubscribes.push(function() { 380 // Get the window object again. When a friendly iframe is destroyed, it 381 // will be null. 382 var win = doc.defaultView; 383 384 if (win) { 385 if (monitoringInterval) { 386 win.clearInterval(monitoringInterval); 387 } 388 removeEvent(win, 'resize', callback, true); 389 } 390 391 removeEvent(doc, 'scroll', callback, true); 392 if (domObserver) { 393 domObserver.disconnect(); 394 } 395 }); 396 397 // Also monitor the parent. 398
398if (doc != (this.root && this.root.ownerDocument || document)) { 399 var frame = getFrameElement(doc); 400 if (frame) { 401 this._monitorIntersections(frame.ownerDocument); 402 } 403 } 404}; 405 406 407/** 408 * Stops polling for intersection changes. 409 * @param {!Document} doc 410 * @private 411 */ 412IntersectionObserver.prototype._unmonitorIntersections = function(doc) { 413 var index = this._monitoringDocuments.indexOf(doc); 414 if (index == -1) { 415 return; 416 } 417 418 var rootDoc = (this.root && this.root.ownerDocument || document); 419 420 // Check if any dependent targets are still remaining. 421 var hasDependentTargets = 422 this._observationTargets.some(function(item) { 423 var itemDoc = item.element.ownerDocument; 424 // Target is in this context. 425 if (itemDoc == doc) { 426 return true; 427 } 428 // Target is nested in this context. 429 while (itemDoc && itemDoc != rootDoc) { 430 var frame = getFrameElement(itemDoc); 431 itemDoc = frame && frame.ownerDocument; 432 if (itemDoc == doc) { 433 return true; 434 } 435 } 436 return false; 437 }); 438 if (hasDependentTargets) { 439 return; 440 } 441 442 // Unsubscribe. 443 var unsubscribe = this._monitoringUnsubscribes[index]; 444 this._monitoringDocuments.splice(index, 1); 445 this._monitoringUnsubscribes.splice(index, 1); 446 unsubscribe(); 447 448 // Also unmonitor the parent. 449 if (doc != rootDoc) { 450 var frame = getFrameElement(doc); 451 if (frame) { 452 this._unmonitorIntersections(frame.ownerDocument); 453 } 454 } 455}; 456 457 458/** 459 * Stops polling for intersection changes. 460 * @param {!Document} doc 461 * @private 462 */ 463IntersectionObserver.prototype._unmonitorAllIntersections = function() { 464 var unsubscribes = this._monitoringUnsubscribes.slice(0); 465 this._monitoringDocuments.length = 0; 466 this._monitoringUnsubscribes.length = 0; 467 for (var i = 0; i < unsubscribes.length; i++) { 468 unsubscribes[i](); 469 } 470}; 471 472 473/** 474 * Scans each observation target for intersection changes and adds them 475 * to the internal entries queue. If new entries are found, it 476 * schedules the callback to be invoked. 477 * @private 478 */ 479IntersectionObserver.prototype._checkForIntersections = function() { 480 if (!this.root && crossOriginUpdater && !crossOriginRect) { 481 // Cross origin monitoring, but no initial data available yet. 482 return; 483 } 484 485 var rootIsInDom = this._rootIsInDom(); 486 var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect(); 487
vendor: 11,761 bytes, lines 488-847
488 this._observationTargets.forEach(function(item) { 489 var target = item.element; 490 var targetRect = getBoundingClientRect(target); 491 var rootContainsTarget = this._rootContainsTarget(target); 492 var oldEntry = item.entry; 493 var intersectionRect = rootIsInDom && rootContainsTarget && 494 this._computeTargetAndRootIntersection(target, targetRect, rootRect); 495 496 var newEntry = item.entry = new IntersectionObserverEntry({ 497 time: now(), 498 target: target, 499 boundingClientRect: targetRect, 500 rootBounds: crossOriginUpdater && !this.root ? null : rootRect, 501 intersectionRect: intersectionRect 502 }); 503 504 if (!oldEntry) { 505 this._queuedEntries.push(newEntry); 506 } else if (rootIsInDom && rootContainsTarget) { 507 // If the new entry intersection ratio has crossed any of the 508 // thresholds, add a new entry. 509 if (this._hasCrossedThreshold(oldEntry, newEntry)) { 510 this._queuedEntries.push(newEntry); 511 } 512 } else { 513 // If the root is not in the DOM or target is not contained within 514 // root but the previous entry for this target had an intersection, 515 // add a new record indicating removal. 516 if (oldEntry && oldEntry.isIntersecting) { 517 this._queuedEntries.push(newEntry); 518 } 519 } 520 }, this); 521 522 if (this._queuedEntries.length) { 523 this._callback(this.takeRecords(), this); 524 } 525}; 526 527 528/** 529 * Accepts a target and root rect computes the intersection between then 530 * following the algorithm in the spec. 531 * https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo 532 * @param {Element} target The target DOM element 533 * @param {Object} targetRect The bounding rect of the target. 534 * @param {Object} rootRect The bounding rect of the root after being 535 * expanded by the rootMargin value. 536 * @return {?Object} The final intersection rect object or undefined if no 537 * intersection is found. 538 * @private 539 */ 540IntersectionObserver.prototype._computeTargetAndRootIntersection = 541 function(target, targetRect, rootRect) { 542 // If the element isn't displayed, an intersection can't happen. 543 if (window.getComputedStyle(target).display == 'none') return; 544 545 var intersectionRect = targetRect; 546 var parent = getParentNode(target); 547 var atRoot = false; 548 549 while (!atRoot && parent) { 550 var parentRect = null; 551 var parentComputedStyle = parent.nodeType == 1 ? 552 window.getComputedStyle(parent) : {}; 553 554 // If the parent isn't displayed, an intersection can't happen. 555 if (parentComputedStyle.display == 'none') return null; 556 557 if (parent == this.root || parent.nodeType == /* DOCUMENT */ 9) { 558 atRoot = true; 559 if (parent == this.root || parent == document) { 560 if (crossOriginUpdater && !this.root) { 561 if (!crossOriginRect || 562 crossOriginRect.width == 0 && crossOriginRect.height == 0) { 563 // A 0-size cross-origin intersection means no-intersection. 564 parent = null; 565 parentRect = null; 566 intersectionRect = null; 567 } else { 568 parentRect = crossOriginRect; 569 } 570 } else { 571 parentRect = rootRect; 572 } 573 } else { 574 // Check if there's a frame that can be navigated to. 575 var frame = getParentNode(parent); 576 var frameRect = frame && getBoundingClientRect(frame); 577 var frameIntersect = 578 frame && 579 this._computeTargetAndRootIntersection(frame, frameRect, rootRect); 580 if (frameRect && frameIntersect) { 581 parent = frame; 582 parentRect = convertFromParentRect(frameRect, frameIntersect); 583 } else { 584 parent = null; 585 intersectionRect = null; 586 } 587 } 588 } else { 589 // If the element has a non-visible overflow, and it's not the <body> 590 // or <html> element, update the intersection rect. 591 // Note: <body> and <html> cannot be clipped to a rect that's not also 592 // the document rect, so no need to compute a new intersection. 593 var doc = parent.ownerDocument; 594 if (parent != doc.body && 595 parent != doc.documentElement && 596 parentComputedStyle.overflow != 'visible') { 597 parentRect = getBoundingClientRect(parent); 598 } 599 } 600 601 // If either of the above conditionals set a new parentRect, 602 // calculate new intersection data. 603 if (parentRect) { 604 intersectionRect = computeRectIntersection(parentRect, intersectionRect); 605 } 606 if (!intersectionRect) break; 607 parent = parent && getParentNode(parent); 608 } 609 return intersectionRect; 610}; 611 612 613/** 614 * Returns the root rect after being expanded by the rootMargin value. 615 * @return {ClientRect} The expanded root rect. 616 * @private 617 */ 618IntersectionObserver.prototype._getRootRect = function() { 619 var rootRect; 620 if (this.root) { 621 rootRect = getBoundingClientRect(this.root); 622 } else { 623 // Use <html>/<body> instead of window since scroll bars affect size. 624 var html = document.documentElement; 625 var body = document.body; 626 rootRect = { 627 top: 0, 628 left: 0, 629 right: html.clientWidth || body.clientWidth, 630 width: html.clientWidth || body.clientWidth, 631 bottom: html.clientHeight || body.clientHeight, 632 height: html.clientHeight || body.clientHeight 633 }; 634 } 635 return this._expandRectByRootMargin(rootRect); 636}; 637 638 639/** 640 * Accepts a rect and expands it by the rootMargin value. 641 * @param {DOMRect|ClientRect} rect The rect object to expand. 642 * @return {ClientRect} The expanded rect. 643 * @private 644 */ 645IntersectionObserver.prototype._expandRectByRootMargin = function(rect) { 646 var margins = this._rootMarginValues.map(function(margin, i) { 647 return margin.unit == 'px' ? margin.value : 648 margin.value * (i % 2 ? rect.width : rect.height) / 100; 649 }); 650 var newRect = { 651 top: rect.top - margins[0], 652 right: rect.right + margins[1], 653 bottom: rect.bottom + margins[2], 654 left: rect.left - margins[3] 655 }; 656 newRect.width = newRect.right - newRect.left; 657 newRect.height = newRect.bottom - newRect.top; 658 659 return newRect; 660}; 661 662 663/** 664 * Accepts an old and new entry and returns true if at least one of the 665 * threshold values has been crossed. 666 * @param {?IntersectionObserverEntry} oldEntry The previous entry for a 667 * particular target element or null if no previous entry exists. 668 * @param {IntersectionObserverEntry} newEntry The current entry for a 669 * particular target element. 670 * @return {boolean} Returns true if a any threshold has been crossed. 671 * @private 672 */ 673IntersectionObserver.prototype._hasCrossedThreshold = 674 function(oldEntry, newEntry) { 675 676 // To make comparing easier, an entry that has a ratio of 0 677 // but does not actually intersect is given a value of -1 678 var oldRatio = oldEntry && oldEntry.isIntersecting ? 679 oldEntry.intersectionRatio || 0 : -1; 680 var newRatio = newEntry.isIntersecting ? 681 newEntry.intersectionRatio || 0 : -1; 682 683 // Ignore unchanged ratios 684 if (oldRatio === newRatio) return; 685 686 for (var i = 0; i < this.thresholds.length; i++) { 687 var threshold = this.thresholds[i]; 688 689 // Return true if an entry matches a threshold or if the new ratio 690 // and the old ratio are on the opposite sides of a threshold. 691 if (threshold == oldRatio || threshold == newRatio || 692 threshold < oldRatio !== threshold < newRatio) { 693 return true; 694 } 695 } 696}; 697 698 699/** 700 * Returns whether or not the root element is an element and is in the DOM. 701 * @return {boolean} True if the root element is an element and is in the DOM. 702 * @private 703 */ 704IntersectionObserver.prototype._rootIsInDom = function() { 705 return !this.root || containsDeep(document, this.root); 706}; 707 708 709/** 710 * Returns whether or not the target element is a child of root. 711 * @param {Element} target The target element to check. 712 * @return {boolean} True if the target element is a child of root. 713 * @private 714 */ 715IntersectionObserver.prototype._rootContainsTarget = function(target) { 716 return containsDeep(this.root || document, target) && 717 (!this.root || this.root.ownerDocument == target.ownerDocument); 718}; 719 720 721/** 722 * Adds the instance to the global IntersectionObserver registry if it isn't 723 * already present. 724 * @private 725 */ 726IntersectionObserver.prototype._registerInstance = function() { 727 if (registry.indexOf(this) < 0) { 728 registry.push(this); 729 } 730}; 731 732 733/** 734 * Removes the instance from the global IntersectionObserver registry. 735 * @private 736 */ 737IntersectionObserver.prototype._unregisterInstance = function() { 738 var index = registry.indexOf(this); 739 if (index != -1) registry.splice(index, 1); 740}; 741 742 743/** 744 * Returns the result of the performance.now() method or null in browsers 745 * that don't support the API. 746 * @return {number} The elapsed time since the page was requested. 747 */ 748function now() { 749 return window.performance && performance.now && performance.now(); 750} 751 752 753/** 754 * Throttles a function and delays its execution, so it's only called at most 755 * once within a given time period. 756 * @param {Function} fn The function to throttle. 757 * @param {number} timeout The amount of time that must pass before the 758 * function can be called again. 759 * @return {Function} The throttled function. 760 */ 761function throttle(fn, timeout) { 762 var timer = null; 763 return function () { 764 if (!timer) { 765 timer = setTimeout(function() { 766 fn(); 767 timer = null; 768 }, timeout); 769 } 770 }; 771} 772 773 774/** 775 * Adds an event handler to a DOM node ensuring cross-browser compatibility. 776 * @param {Node} node The DOM node to add the event handler to. 777 * @param {string} event The event name. 778 * @param {Function} fn The event handler to add. 779 * @param {boolean} opt_useCapture Optionally adds the even to the capture 780 * phase. Note: this only works in modern browsers. 781 */ 782function addEvent(node, event, fn, opt_useCapture) { 783 if (typeof node.addEventListener == 'function') { 784 node.addEventListener(event, fn, opt_useCapture || false); 785 } 786 else if (typeof node.attachEvent == 'function') { 787 node.attachEvent('on' + event, fn); 788 } 789} 790 791 792/** 793 * Removes a previously added event handler from a DOM node. 794 * @param {Node} node The DOM node to remove the event handler from. 795 * @param {string} event The event name. 796 * @param {Function} fn The event handler to remove. 797 * @param {boolean} opt_useCapture If the event handler was added with this 798 * flag set to true, it should be set to true here in order to remove it. 799 */ 800function removeEvent(node, event, fn, opt_useCapture) { 801 if (typeof node.removeEventListener == 'function') { 802 node.removeEventListener(event, fn, opt_useCapture || false); 803 } 804 else if (typeof node.detatchEvent == 'function') { 805 node.detatchEvent('on' + event, fn); 806 } 807} 808 809 810/** 811 * Returns the intersection between two rect objects. 812 * @param {Object} rect1 The first rect. 813 * @param {Object} rect2 The second rect. 814 * @return {?Object|?ClientRect} The intersection rect or undefined if no 815 * intersection is found. 816 */ 817function computeRectIntersection(rect1, rect2) { 818 var top = Math.max(rect1.top, rect2.top); 819 var bottom = Math.min(rect1.bottom, rect2.bottom); 820 var left = Math.max(rect1.left, rect2.left); 821 var right = Math.min(rect1.right, rect2.right); 822 var width = right - left; 823 var height = bottom - top; 824 825 return (width >= 0 && height >= 0) && { 826 top: top, 827 bottom: bottom, 828 left: left, 829 right: right, 830 width: width, 831 height: height 832 } || null; 833} 834 835 836/** 837 * Shims the native getBoundingClientRect for compatibility with older IE. 838 * @param {Element} el The element whose bounding rect to get. 839 * @return {DOMRect|ClientRect} The (possibly shimmed) rect of the element. 840 */ 841function getBoundingClientRect(el) { 842 var rect; 843 844 try { 845 rect = el.getBoundingClientRect(); 846 } catch (err) { 847 // Ignore Windows 7 IE11 "Unspecified error"
848 // https://github.com/w3c/IntersectionObserver/pull/205 849 } 850 851 if (!rect) return getEmptyRect(); 852 853 // Older IE 854 if (!(rect.width && rect.height)) { 855 rect = { 856 top: rect.top, 857 right: rect.right, 858 bottom: rect.bottom, 859 left: rect.left, 860 width: rect.right - rect.left, 861 height: rect.bottom - rect.top 862 }; 863 } 864 return rect; 865} 866 867 868/** 869 * Returns an empty rect object. An empty rect is returned when an element 870 * is not in the DOM. 871 * @return {ClientRect} The empty rect. 872 */ 873function getEmptyRect() { 874 return { 875 top: 0, 876 bottom: 0, 877 left: 0, 878 right: 0, 879 width: 0, 880 height: 0 881 }; 882} 883 884 885/** 886 * Ensure that the result has all of the necessary fields of the DOMRect. 887 * Specifically this ensures that `x` and `y` fields are set. 888 * 889 * @param {?DOMRect|?ClientRect} rect 890 * @return {?DOMRect} 891 */ 892function ensureDOMRect(rect) { 893 // A `DOMRect` object has `x` and `y` fields. 894 if (!rect || 'x' in rect) { 895 return rect; 896 } 897 // A IE's `ClientRect` type does not have `x` and `y`. The same is the case 898 // for internally calculated Rect objects. For the purposes of 899 // `IntersectionObserver`, it's sufficient to simply mirror `left` and `top` 900 // for these fields. 901 return { 902 top: rect.top, 903 y: rect.top, 904 bottom: rect.bottom, 905 left: rect.left, 906 x: rect.left, 907 right: rect.right, 908 width: rect.width, 909 height: rect.height 910 }; 911} 912 913 914/** 915 * Inverts the intersection and bounding rect from the parent (frame) BCR to 916 * the local BCR space. 917 * @param {DOMRect|ClientRect} parentBoundingRect The parent's bound client rect. 918 * @param {DOMRect|ClientRect} parentIntersectionRect The parent's own intersection rect. 919 * @return {ClientRect} The local root bounding rect for the parent's children. 920 */ 921function convertFromParentRect(parentBoundingRect, parentIntersectionRect) { 922 var top = parentIntersectionRect.top - parentBoundingRect.top; 923 var left = parentIntersectionRect.left - parentBoundingRect.left; 924 return { 925 top: top, 926 left: left, 927 height: parentIntersectionRect.height, 928 width: parentIntersectionRect.width, 929 bottom: top + parentIntersectionRect.height, 930 right: left + parentIntersectionRect.width 931 }; 932} 933 934 935/** 936 * Checks to see if a parent element contains a child element (including inside 937 * shadow DOM). 938 * @param {Node} parent The parent element. 939 * @param {Node} child The child element. 940 * @return {boolean} True if the parent node contains the child node. 941 */ 942function containsDeep(parent, child) { 943 var node = child; 944 while (node) { 945 if (node == parent) return true; 946 947 node = getParentNode(node); 948 } 949 return false; 950} 951 952 953/** 954 * Gets the parent node of an element or its host element if the parent node 955 * is a shadow root. 956 * @param {Node} node The node whose parent to get. 957 * @return {Node|null} The parent node or null if no parent exists. 958 */ 959function getParentNode(node) { 960 var parent = node.parentNode; 961 962 if (node.nodeType == /* DOCUMENT */ 9 && node != document) { 963 // If this node is a document node, look for the embedding frame. 964 return getFrameElement(node); 965 } 966 967 if (parent && parent.nodeType == 11 && parent.host) { 968 // If the parent is a shadow root, return the host element. 969 return parent.host; 970 } 971 972 if (parent && parent.assignedSlot) { 973 // If the parent is distributed in a <slot>, return the parent of a slot. 974 return parent.assignedSlot.parentNode; 975 } 976 977 return parent; 978} 979 980 981// Exposes the constructors globally. 982window.IntersectionObserver = IntersectionObserver; 983window.IntersectionObserverEntry = IntersectionObserverEntry; 984 985}());
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.