{ root: Element, rootMargin: string, threshold: number | number[] }
在构造函数中首先会调用私有方法解析我们传入的rootMargin属性,下面是这个方法的基本实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
IntersectionObserver.prototype._parseRootMargin = function(opt_rootMargin) { var marginString = opt_rootMargin || '0px'; var margins = marginString.split(/\s+/).map(function(margin) { var parts = /^(-?\d*\.?\d+)(px|%)$/.exec(margin); if (!parts) { thrownewError('rootMargin must be specified in pixels or percent'); } return {value: parseFloat(parts[1]), unit: parts[2]}; });
IntersectionObserver.prototype._initThresholds = function(opt_threshold) { var threshold = opt_threshold || [0]; if (!Array.isArray(threshold)) threshold = [threshold];
return threshold.sort().filter(function(t, i, a) { if (typeof t != 'number' || isNaN(t) || t < 0 || t > 1) { thrownewError('threshold must be a number between 0 and 1 inclusively'); } return t !== a[i - 1]; }); }
IntersectionObserver.prototype._monitorIntersections = function() { if (!this._monitoringIntersections) { this._monitoringIntersections = true;
// If a poll interval is set, use polling instead of listening to // resize and scroll events or DOM mutations. if (this.POLL_INTERVAL) { this._monitoringInterval = setInterval( this._checkForIntersections, this.POLL_INTERVAL); } else { addEvent(window, 'resize', this._checkForIntersections, true); addEvent(document, 'scroll', this._checkForIntersections, true);
IntersectionObserver.prototype._checkForIntersections = function() { var rootIsInDom = this._rootIsInDom(); var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect();
this._observationTargets.forEach(function(item) { var target = item.element; var targetRect = getBoundingClientRect(target); var rootContainsTarget = this._rootContainsTarget(target); var oldEntry = item.entry; var intersectionRect = rootIsInDom && rootContainsTarget && this._computeTargetAndRootIntersection(target, rootRect);
var newEntry = item.entry = new IntersectionObserverEntry({ time: now(), target: target, boundingClientRect: targetRect, rootBounds: rootRect, intersectionRect: intersectionRect });
if (!oldEntry) { this._queuedEntries.push(newEntry); } elseif (rootIsInDom && rootContainsTarget) { // If the new entry intersection ratio has crossed any of the // thresholds, add a new entry. if (this._hasCrossedThreshold(oldEntry, newEntry)) { this._queuedEntries.push(newEntry); } } else { // If the root is not in the DOM or target is not contained within // root but the previous entry for this target had an intersection, // add a new record indicating removal. if (oldEntry && oldEntry.isIntersecting) { this._queuedEntries.push(newEntry); } } }, this);
if (this._queuedEntries.length) { this._callback(this.takeRecords(), this); } }
functioncontainsDeep(parent, child) { var node = child; while (node) { if (node == parent) returntrue;
node = getParentNode(node); } returnfalse; }
functiongetParentNode(node) { var parent = node.parentNode;
if (parent && parent.nodeType == 11 && parent.host) { // If the parent is a shadow root, return the host element. return parent.host; }
if (parent && parent.assignedSlot) { // If the parent is distributed in a <slot>, return the parent of a slot. return parent.assignedSlot.parentNode; }
this._observationTargets.forEach(function(item) { var target = item.element; var targetRect = getBoundingClientRect(target); var rootContainsTarget = this._rootContainsTarget(target); var oldEntry = item.entry; var intersectionRect = rootIsInDom && rootContainsTarget && this._computeTargetAndRootIntersection(target, rootRect);
var newEntry = item.entry = new IntersectionObserverEntry({ time: now(), target: target, boundingClientRect: targetRect, rootBounds: rootRect, intersectionRect: intersectionRect });
if (!oldEntry) { this._queuedEntries.push(newEntry); } elseif (rootIsInDom && rootContainsTarget) { // If the new entry intersection ratio has crossed any of the // thresholds, add a new entry. if (this._hasCrossedThreshold(oldEntry, newEntry)) { this._queuedEntries.push(newEntry); } } else { // If the root is not in the DOM or target is not contained within // root but the previous entry for this target had an intersection, // add a new record indicating removal. if (oldEntry && oldEntry.isIntersecting) { this._queuedEntries.push(newEntry); } } }, this);
// If the element isn't displayed, an intersection can't happen. if (window.getComputedStyle(target).display == 'none') return;
var targetRect = getBoundingClientRect(target); var intersectionRect = targetRect; var parent = getParentNode(target); var atRoot = false;
while (!atRoot) { var parentRect = null; var parentComputedStyle = parent.nodeType == 1 ? window.getComputedStyle(parent) : {};
// If the parent isn't displayed, an intersection can't happen. if (parentComputedStyle.display == 'none') return;
if (parent == this.root || parent == document) { atRoot = true; parentRect = rootRect; } else { // If the element has a non-visible overflow, and it's not the <body> // or <html> element, update the intersection rect. // Note: <body> and <html> cannot be clipped to a rect that's not also // the document rect, so no need to compute a new intersection. if (parent != document.body && parent != document.documentElement && parentComputedStyle.overflow != 'visible') { parentRect = getBoundingClientRect(parent); } }
// If either of the above conditionals set a new parentRect, // calculate new intersection data. if (parentRect) { intersectionRect = computeRectIntersection(parentRect, intersectionRect);
functioncomputeRectIntersection(rect1, rect2) { var top = Math.max(rect1.top, rect2.top); var bottom = Math.min(rect1.bottom, rect2.bottom); var left = Math.max(rect1.left, rect2.left); var right = Math.min(rect1.right, rect2.right); var width = right - left; var height = bottom - top;
// Calculates the intersection ratio. var targetRect = this.boundingClientRect; var targetArea = targetRect.width * targetRect.height; var intersectionRect = this.intersectionRect; var intersectionArea = intersectionRect.width * intersectionRect.height;
// Sets intersection ratio. if (targetArea) { // Round the intersection ratio to avoid floating point math issues: // https://github.com/w3c/IntersectionObserver/issues/324 this.intersectionRatio = Number((intersectionArea / targetArea).toFixed(4)); } else { // If area is zero and is intersecting, sets to 1, otherwise to 0 this.intersectionRatio = this.isIntersecting ? 1 : 0; } }
if (targetArea) { // Round the intersection ratio to avoid floating point math issues: // https://github.com/w3c/IntersectionObserver/issues/324 this.intersectionRatio = Number((intersectionArea / targetArea).toFixed(4)); } else { // If area is zero and is intersecting, sets to 1, otherwise to 0 this.intersectionRatio = this.isIntersecting ? 1 : 0; }
// To make comparing easier, an entry that has a ratio of 0 // but does not actually intersect is given a value of -1 var oldRatio = oldEntry && oldEntry.isIntersecting ? oldEntry.intersectionRatio || 0 : -1; var newRatio = newEntry.isIntersecting ? newEntry.intersectionRatio || 0 : -1;
// Ignore unchanged ratios if (oldRatio === newRatio) return;
for (var i = 0; i < this.thresholds.length; i++) { var threshold = this.thresholds[i];
// Return true if an entry matches a threshold or if the new ratio // and the old ratio are on the opposite sides of a threshold. if (threshold == oldRatio || threshold == newRatio || threshold < oldRatio !== threshold < newRatio) { returntrue; } } }