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.