You're Invited: Meet the Socket team at BSidesSF and RSAC - April 27 - May 1.RSVP
Socket
Sign inDemoInstall
Socket

intersection-observer

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

intersection-observer - npm Package Compare versions

Comparing version

to
0.8.0

651

intersection-observer-test.js

@@ -944,2 +944,653 @@ /**

describe('same-origin iframe', function() {
var iframe, win, doc;
var iframeTargetEl1, iframeTargetEl2;
var bodyWidth;
beforeEach(function(done) {
iframe = document.createElement('iframe');
iframe.setAttribute('frameborder', '0');
iframe.setAttribute('scrolling', 'yes');
iframe.style.position = 'fixed';
iframe.style.top = '0px';
iframe.style.width = '100px';
iframe.style.height = '200px';
iframe.onerror = function() {
done(new Error('iframe initialization failed'));
};
iframe.onload = function() {
iframe.onload = null;
iframeWin = iframe.contentWindow;
iframeDoc = iframeWin.document;
iframeDoc.open();
iframeDoc.write('<!DOCTYPE html><html><body>');
iframeDoc.write('<style>');
iframeDoc.write('body {margin: 0}');
iframeDoc.write('.target {height: 200px; margin-bottom: 2px; background: blue;}');
iframeDoc.write('</style>');
iframeDoc.close();
function createTarget(id, bg) {
var target = iframeDoc.createElement('div');
target.id = id;
target.className = 'target';
target.style.background = bg;
iframeDoc.body.appendChild(target);
return target;
}
iframeTargetEl1 = createTarget('target1', 'blue');
iframeTargetEl2 = createTarget('target2', 'green');
bodyWidth = iframeDoc.body.clientWidth;
done();
};
iframe.src = 'about:blank';
rootEl.appendChild(iframe);
});
afterEach(function() {
rootEl.removeChild(iframe);
});
function rect(r) {
return {
top: r.top,
left: r.left,
width: r.width != null ? r.width : r.right - r.left,
height: r.height != null ? r.height : r.bottom - r.top,
right: r.right != null ? r.right : r.left + r.width,
bottom: r.bottom != null ? r.bottom : r.top + r.height
};
}
function getRootRect(doc) {
var html = doc.documentElement;
var body = doc.body;
return rect({
top: 0,
left: 0,
right: html.clientWidth || body.clientWidth,
width: html.clientWidth || body.clientWidth,
bottom: html.clientHeight || body.clientHeight,
height: html.clientHeight || body.clientHeight
});
}
it('iframe targets do not intersect with a top root element', function(done) {
var io = new IntersectionObserver(function(unsortedRecords) {
var records = sortRecords(unsortedRecords);
expect(records.length).to.be(2);
expect(records[0].isIntersecting).to.be(false);
expect(records[1].isIntersecting).to.be(false);
done();
io.disconnect();
}, {root: rootEl});
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
});
it('triggers for all targets in top-level root', function(done) {
var io = new IntersectionObserver(function(unsortedRecords) {
var records = sortRecords(unsortedRecords);
expect(records.length).to.be(2);
expect(records[0].isIntersecting).to.be(true);
expect(records[0].intersectionRatio).to.be(1);
expect(records[1].isIntersecting).to.be(false);
expect(records[1].intersectionRatio).to.be(0);
// The rootBounds is for the document's root.
expect(records[0].rootBounds.height).to.be(innerHeight);
done();
io.disconnect();
});
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
});
it('triggers for all targets in iframe-level root', function(done) {
var io = new IntersectionObserver(function(unsortedRecords) {
var records = sortRecords(unsortedRecords);
expect(records.length).to.be(2);
expect(records[0].intersectionRatio).to.be(1);
expect(records[1].intersectionRatio).to.be(1);
// The rootBounds is for the document's root.
expect(rect(records[0].rootBounds)).
to.eql(rect(iframeDoc.body.getBoundingClientRect()));
done();
io.disconnect();
}, {root: iframeDoc.body});
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
});
it('calculates rects for a fully visible frame', function(done) {
iframe.style.top = '0px';
iframe.style.height = '300px';
var io = new IntersectionObserver(function(unsortedRecords) {
var records = sortRecords(unsortedRecords);
expect(records.length).to.be(2);
expect(rect(records[0].rootBounds)).to.eql(getRootRect(document));
expect(rect(records[1].rootBounds)).to.eql(getRootRect(document));
// The target1 is fully visible.
var clientRect1 = rect({
top: 0,
left: 0,
width: bodyWidth,
height: 200
});
expect(rect(records[0].boundingClientRect)).to.eql(clientRect1);
expect(rect(records[0].intersectionRect)).to.eql(clientRect1);
expect(records[0].isIntersecting).to.be(true);
expect(records[0].intersectionRatio).to.be(1);
// The target2 is partially visible.
var clientRect2 = rect({
top: 202,
left: 0,
width: bodyWidth,
height: 200
});
var intersectRect2 = rect({
top: 202,
left: 0,
width: bodyWidth,
// The bottom is clipped off.
bottom: 300
});
expect(rect(records[1].boundingClientRect)).to.eql(clientRect2);
expect(rect(records[1].intersectionRect)).to.eql(intersectRect2);
expect(records[1].isIntersecting).to.be(true);
expect(records[1].intersectionRatio).to.be.within(0.48, 0.5); // ~0.5
done();
io.disconnect();
});
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
});
it('calculates rects for a fully visible and offset frame', function(done) {
iframe.style.top = '10px';
iframe.style.height = '300px';
var io = new IntersectionObserver(function(unsortedRecords) {
var records = sortRecords(unsortedRecords);
expect(records.length).to.be(2);
expect(rect(records[0].rootBounds)).to.eql(getRootRect(document));
expect(rect(records[1].rootBounds)).to.eql(getRootRect(document));
// The target1 is fully visible.
var clientRect1 = rect({
top: 0,
left: 0,
width: bodyWidth,
height: 200
});
expect(rect(records[0].boundingClientRect)).to.eql(clientRect1);
expect(rect(records[0].intersectionRect)).to.eql(clientRect1);
expect(records[0].isIntersecting).to.be(true);
expect(records[0].intersectionRatio).to.be(1);
// The target2 is partially visible.
var clientRect2 = rect({
top: 202,
left: 0,
width: bodyWidth,
height: 200
});
var intersectRect2 = rect({
top: 202,
left: 0,
width: bodyWidth,
// The bottom is clipped off.
bottom: 300
});
expect(rect(records[1].boundingClientRect)).to.eql(clientRect2);
expect(rect(records[1].intersectionRect)).to.eql(intersectRect2);
expect(records[1].isIntersecting).to.be(true);
expect(records[1].intersectionRatio).to.be.within(0.48, 0.5); // ~0.5
done();
io.disconnect();
});
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
});
it('calculates rects for a clipped frame on top', function(done) {
iframe.style.top = '-10px';
iframe.style.height = '300px';
var io = new IntersectionObserver(function(unsortedRecords) {
var records = sortRecords(unsortedRecords);
expect(records.length).to.be(2);
expect(rect(records[0].rootBounds)).to.eql(getRootRect(document));
expect(rect(records[1].rootBounds)).to.eql(getRootRect(document));
// The target1 is clipped at the top by the iframe's clipping.
var clientRect1 = rect({
top: 0,
left: 0,
width: bodyWidth,
height: 200
});
var intersectRect1 = rect({
left: 0,
width: bodyWidth,
// Top is clipped.
top: 10,
height: 200 - 10
});
expect(rect(records[0].boundingClientRect)).to.eql(clientRect1);
expect(rect(records[0].intersectionRect)).to.eql(intersectRect1);
expect(records[0].isIntersecting).to.be(true);
expect(records[0].intersectionRatio).to.within(0.94, 0.96); // ~0.95
// The target2 is partially visible.
var clientRect2 = rect({
top: 202,
left: 0,
width: bodyWidth,
height: 200
});
var intersectRect2 = rect({
top: 202,
left: 0,
width: bodyWidth,
// The bottom is clipped off.
bottom: 300
});
expect(rect(records[1].boundingClientRect)).to.eql(clientRect2);
expect(rect(records[1].intersectionRect)).to.eql(intersectRect2);
expect(records[1].isIntersecting).to.be(true);
expect(records[1].intersectionRatio).to.be.within(0.48, 0.5); // ~0.49
done();
io.disconnect();
});
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
});
it('calculates rects for a clipped frame on bottom', function(done) {
iframe.style.top = 'auto';
iframe.style.bottom = '-10px';
iframe.style.height = '300px';
var io = new IntersectionObserver(function(unsortedRecords) {
var records = sortRecords(unsortedRecords);
expect(records.length).to.be(2);
expect(rect(records[0].rootBounds)).to.eql(getRootRect(document));
expect(rect(records[1].rootBounds)).to.eql(getRootRect(document));
// The target1 is clipped at the top by the iframe's clipping.
var clientRect1 = rect({
top: 0,
left: 0,
width: bodyWidth,
height: 200
});
expect(rect(records[0].boundingClientRect)).to.eql(clientRect1);
expect(rect(records[0].intersectionRect)).to.eql(clientRect1);
expect(records[0].isIntersecting).to.be(true);
expect(records[0].intersectionRatio).to.be(1);
// The target2 is partially visible.
var clientRect2 = rect({
top: 202,
left: 0,
width: bodyWidth,
height: 200
});
var intersectRect2 = rect({
top: 202,
left: 0,
width: bodyWidth,
// The bottom is clipped off.
bottom: 300 - 10
});
expect(rect(records[1].boundingClientRect)).to.eql(clientRect2);
expect(rect(records[1].intersectionRect)).to.eql(intersectRect2);
expect(records[1].isIntersecting).to.be(true);
expect(records[1].intersectionRatio).to.be.within(0.43, 0.45); // ~0.44
done();
io.disconnect();
});
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
});
it('calculates rects for a fully visible frame and scrolled', function(done) {
iframe.style.top = '0px';
iframe.style.height = '300px';
iframeWin.scrollTo(0, 10);
var io = new IntersectionObserver(function(unsortedRecords) {
var records = sortRecords(unsortedRecords);
expect(records.length).to.be(2);
expect(rect(records[0].rootBounds)).to.eql(getRootRect(document));
expect(rect(records[1].rootBounds)).to.eql(getRootRect(document));
// The target1 is fully visible.
var clientRect1 = rect({
top: -10,
left: 0,
width: bodyWidth,
height: 200
});
var intersectRect1 = rect({
top: 0,
left: 0,
width: bodyWidth,
// Height is only for the visible area.
height: 200 - 10
});
expect(rect(records[0].boundingClientRect)).to.eql(clientRect1);
expect(rect(records[0].intersectionRect)).to.eql(intersectRect1);
expect(records[0].isIntersecting).to.be(true);
expect(records[0].intersectionRatio).to.within(0.94, 0.96); // ~0.95
// The target2 is partially visible.
var clientRect2 = rect({
top: 202 - 10,
left: 0,
width: bodyWidth,
height: 200
});
var intersectRect2 = rect({
top: 202 - 10,
left: 0,
width: bodyWidth,
// The bottom is clipped off.
bottom: 300
});
expect(rect(records[1].boundingClientRect)).to.eql(clientRect2);
expect(rect(records[1].intersectionRect)).to.eql(intersectRect2);
expect(records[1].isIntersecting).to.be(true);
expect(records[1].intersectionRatio).to.be.within(0.53, 0.55); // ~0.54
done();
io.disconnect();
});
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
});
it('calculates rects for a clipped frame on top and scrolled', function(done) {
iframe.style.top = '-10px';
iframe.style.height = '300px';
iframeWin.scrollTo(0, 10);
var io = new IntersectionObserver(function(unsortedRecords) {
var records = sortRecords(unsortedRecords);
expect(records.length).to.be(2);
expect(rect(records[0].rootBounds)).to.eql(getRootRect(document));
expect(rect(records[1].rootBounds)).to.eql(getRootRect(document));
// The target1 is clipped at the top by the iframe's clipping.
var clientRect1 = rect({
top: -10,
left: 0,
width: bodyWidth,
height: 200
});
var intersectRect1 = rect({
left: 0,
width: bodyWidth,
// Top is clipped.
top: 10,
// The height is less by both: offset and scroll.
height: 200 - 10 - 10
});
expect(rect(records[0].boundingClientRect)).to.eql(clientRect1);
expect(rect(records[0].intersectionRect)).to.eql(intersectRect1);
expect(records[0].isIntersecting).to.be(true);
expect(records[0].intersectionRatio).to.within(0.89, 0.91); // ~0.9
// The target2 is partially visible.
var clientRect2 = rect({
top: 202 - 10,
left: 0,
width: bodyWidth,
height: 200
});
var intersectRect2 = rect({
top: 202 - 10,
left: 0,
width: bodyWidth,
// The bottom is clipped off.
bottom: 300
});
expect(rect(records[1].boundingClientRect)).to.eql(clientRect2);
expect(rect(records[1].intersectionRect)).to.eql(intersectRect2);
expect(records[1].isIntersecting).to.be(true);
expect(records[1].intersectionRatio).to.be.within(0.53, 0.55); // ~0.54
done();
io.disconnect();
});
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
});
it('handles style changes', function(done) {
var spy = sinon.spy();
// When first element becomes invisible, the second element will show.
// And in reverse: when the first element becomes visible again, the
// second element will disappear.
var io = new IntersectionObserver(spy);
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
runSequence([
function(done) {
setTimeout(function() {
expect(spy.callCount).to.be(1);
var records = sortRecords(spy.lastCall.args[0]);
expect(records.length).to.be(2);
expect(records[0].intersectionRatio).to.be(1);
expect(records[0].isIntersecting).to.be(true);
expect(records[1].intersectionRatio).to.be(0);
expect(records[1].isIntersecting).to.be(false);
done();
}, ASYNC_TIMEOUT);
},
function(done) {
iframeTargetEl1.style.display = 'none';
setTimeout(function() {
expect(spy.callCount).to.be(2);
var records = sortRecords(spy.lastCall.args[0]);
expect(records.length).to.be(2);
expect(records[0].intersectionRatio).to.be(0);
expect(records[0].isIntersecting).to.be(false);
expect(records[1].intersectionRatio).to.be(1);
expect(records[1].isIntersecting).to.be(true);
done();
}, ASYNC_TIMEOUT);
},
function(done) {
iframeTargetEl1.style.display = '';
setTimeout(function() {
expect(spy.callCount).to.be(3);
var records = sortRecords(spy.lastCall.args[0]);
expect(records.length).to.be(2);
expect(records[0].intersectionRatio).to.be(1);
expect(records[0].isIntersecting).to.be(true);
expect(records[1].intersectionRatio).to.be(0);
expect(records[1].isIntersecting).to.be(false);
done();
}, ASYNC_TIMEOUT);
},
function(done) {
io.disconnect();
done();
}
], done);
});
it('handles scroll changes', function(done) {
var spy = sinon.spy();
// Scrolling to the middle of the iframe shows the second box and
// hides the first.
var io = new IntersectionObserver(spy);
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
runSequence([
function(done) {
setTimeout(function() {
expect(spy.callCount).to.be(1);
var records = sortRecords(spy.lastCall.args[0]);
expect(records.length).to.be(2);
expect(records[0].intersectionRatio).to.be(1);
expect(records[0].isIntersecting).to.be(true);
expect(records[1].intersectionRatio).to.be(0);
expect(records[1].isIntersecting).to.be(false);
done();
}, ASYNC_TIMEOUT);
},
function(done) {
iframeWin.scrollTo(0, 202);
setTimeout(function() {
expect(spy.callCount).to.be(2);
var records = sortRecords(spy.lastCall.args[0]);
expect(records.length).to.be(2);
expect(records[0].intersectionRatio).to.be(0);
expect(records[0].isIntersecting).to.be(false);
expect(records[1].intersectionRatio).to.be(1);
expect(records[1].isIntersecting).to.be(true);
done();
}, ASYNC_TIMEOUT);
},
function(done) {
iframeWin.scrollTo(0, 0);
setTimeout(function() {
expect(spy.callCount).to.be(3);
var records = sortRecords(spy.lastCall.args[0]);
expect(records.length).to.be(2);
expect(records[0].intersectionRatio).to.be(1);
expect(records[0].isIntersecting).to.be(true);
expect(records[1].intersectionRatio).to.be(0);
expect(records[1].isIntersecting).to.be(false);
done();
}, ASYNC_TIMEOUT);
},
function(done) {
io.disconnect();
done();
}
], done);
});
it('handles iframe changes', function(done) {
var spy = sinon.spy();
// Iframe goes off screen and returns.
var io = new IntersectionObserver(spy);
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
runSequence([
function(done) {
setTimeout(function() {
expect(spy.callCount).to.be(1);
var records = sortRecords(spy.lastCall.args[0]);
expect(records.length).to.be(2);
expect(records[0].intersectionRatio).to.be(1);
expect(records[0].isIntersecting).to.be(true);
expect(records[1].intersectionRatio).to.be(0);
expect(records[1].isIntersecting).to.be(false);
// Top-level bounds.
expect(records[0].rootBounds.height).to.be(innerHeight);
expect(records[0].intersectionRect.height).to.be(200);
done();
}, ASYNC_TIMEOUT);
},
function(done) {
// Completely off screen.
iframe.style.top = '-202px';
setTimeout(function() {
expect(spy.callCount).to.be(2);
var records = sortRecords(spy.lastCall.args[0]);
expect(records.length).to.be(1);
expect(records[0].intersectionRatio).to.be(0);
expect(records[0].isIntersecting).to.be(false);
// Top-level bounds.
expect(records[0].rootBounds.height).to.be(innerHeight);
expect(records[0].intersectionRect.height).to.be(0);
done();
}, ASYNC_TIMEOUT);
},
function(done) {
// Partially returns.
iframe.style.top = '-100px';
setTimeout(function() {
expect(spy.callCount).to.be(3);
var records = sortRecords(spy.lastCall.args[0]);
expect(records.length).to.be(1);
expect(records[0].intersectionRatio).to.be.within(0.45, 0.55);
expect(records[0].isIntersecting).to.be(true);
// Top-level bounds.
expect(records[0].rootBounds.height).to.be(innerHeight);
expect(records[0].intersectionRect.height / 200).to.be.within(0.45, 0.55);
done();
}, ASYNC_TIMEOUT);
},
function(done) {
io.disconnect();
done();
}
], done);
});
it('continues to monitor until the last target unobserved', function(done) {
var spy = sinon.spy();
// Iframe goes off screen and returns.
var io = new IntersectionObserver(spy);
io.observe(target1);
io.observe(iframeTargetEl1);
io.observe(iframeTargetEl2);
runSequence([
function(done) {
setTimeout(function() {
expect(spy.callCount).to.be(1);
expect(spy.lastCall.args[0].length).to.be(3);
// Unobserve one from the main context and one from iframe.
io.unobserve(target1);
io.unobserve(iframeTargetEl2);
done();
}, ASYNC_TIMEOUT);
},
function(done) {
// Completely off screen.
iframe.style.top = '-202px';
setTimeout(function() {
expect(spy.callCount).to.be(2);
expect(spy.lastCall.args[0].length).to.be(1);
io.unobserve(iframeTargetEl1);
done();
}, ASYNC_TIMEOUT);
},
function(done) {
// Partially returns.
iframe.style.top = '-100px';
setTimeout(function() {
expect(spy.callCount).to.be(2);
done();
}, ASYNC_TIMEOUT);
},
function(done) {
io.disconnect();
done();
}
], done);
});
});
});

@@ -946,0 +1597,0 @@

244

intersection-observer.js

@@ -121,2 +121,7 @@ /**

}).join(' ');
/** @private @const {!Array<!Document>} */
this._monitoringDocuments = [];
/** @private @const {!Array<function()>} */
this._monitoringUnsubscribes = [];
}

@@ -166,3 +171,3 @@

this._observationTargets.push({element: target, entry: null});
this._monitorIntersections();
this._monitorIntersections(target.ownerDocument);
this._checkForIntersections();

@@ -179,7 +184,6 @@ };

this._observationTargets.filter(function(item) {
return item.element != target;
});
if (!this._observationTargets.length) {
this._unmonitorIntersections();
return item.element != target;
});
this._unmonitorIntersections(target.ownerDocument);
if (this._observationTargets.length == 0) {
this._unregisterInstance();

@@ -195,3 +199,3 @@ }

this._observationTargets = [];
this._unmonitorIntersections();
this._unmonitorAllIntersections();
this._unregisterInstance();

@@ -269,28 +273,64 @@ };

* happening, and if the page's visibility state is visible.
* @param {!Document} doc
* @private
*/
IntersectionObserver.prototype._monitorIntersections = function() {
if (!this._monitoringIntersections) {
this._monitoringIntersections = true;
IntersectionObserver.prototype._monitorIntersections = function(doc) {
var win = doc.defaultView;
if (!win) {
// Already destroyed.
return;
}
if (this._monitoringDocuments.indexOf(doc) != -1) {
// Already monitoring.
return;
}
// 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);
// Private state for monitoring.
var callback = this._checkForIntersections;
var monitoringInterval = null;
var domObserver = null;
// If a poll interval is set, use polling instead of listening to
// resize and scroll events or DOM mutations.
if (this.POLL_INTERVAL) {
monitoringInterval = win.setInterval(callback, this.POLL_INTERVAL);
} else {
addEvent(win, 'resize', callback, true);
addEvent(doc, 'scroll', callback, true);
if (this.USE_MUTATION_OBSERVER && 'MutationObserver' in win) {
domObserver = new win.MutationObserver(callback);
domObserver.observe(doc, {
attributes: true,
childList: true,
characterData: true,
subtree: true
});
}
else {
addEvent(window, 'resize', this._checkForIntersections, true);
addEvent(document, 'scroll', this._checkForIntersections, true);
}
if (this.USE_MUTATION_OBSERVER && 'MutationObserver' in window) {
this._domObserver = new MutationObserver(this._checkForIntersections);
this._domObserver.observe(document, {
attributes: true,
childList: true,
characterData: true,
subtree: true
});
this._monitoringDocuments.push(doc);
this._monitoringUnsubscribes.push(function() {
// Get the window object again. When a friendly iframe is destroyed, it
// will be null.
var win = doc.defaultView;
if (win) {
if (monitoringInterval) {
win.clearInterval(monitoringInterval);
}
removeEvent(win, 'resize', callback, true);
}
removeEvent(doc, 'scroll', callback, true);
if (domObserver) {
domObserver.disconnect();
}
});
// Also monitor the parent.
if (doc != (this.root && this.root.ownerDocument || document)) {
var frame = getFrameElement(doc);
if (frame) {
this._monitorIntersections(frame.ownerDocument);
}
}

@@ -302,17 +342,46 @@ };

* Stops polling for intersection changes.
* @param {!Document} doc
* @private
*/
IntersectionObserver.prototype._unmonitorIntersections = function() {
if (this._monitoringIntersections) {
this._monitoringIntersections = false;
IntersectionObserver.prototype._unmonitorIntersections = function(doc) {
var index = this._monitoringDocuments.indexOf(doc);
if (index == -1) {
return;
}
clearInterval(this._monitoringInterval);
this._monitoringInterval = null;
var rootDoc = (this.root && this.root.ownerDocument || document);
removeEvent(window, 'resize', this._checkForIntersections, true);
removeEvent(document, 'scroll', this._checkForIntersections, true);
// Check if any dependent targets are still remaining.
var hasDependentTargets =
this._observationTargets.some(function(item) {
var itemDoc = item.element.ownerDocument;
// Target is in this context.
if (itemDoc == doc) {
return true;
}
// Target is nested in this context.
while (itemDoc && itemDoc != rootDoc) {
var frame = getFrameElement(itemDoc);
itemDoc = frame && frame.ownerDocument;
if (itemDoc == doc) {
return true;
}
}
return false;
});
if (hasDependentTargets) {
return;
}
if (this._domObserver) {
this._domObserver.disconnect();
this._domObserver = null;
// Unsubscribe.
var unsubscribe = this._monitoringUnsubscribes[index];
this._monitoringDocuments.splice(index, 1);
this._monitoringUnsubscribes.splice(index, 1);
unsubscribe();
// Also unmonitor the parent.
if (doc != rootDoc) {
var frame = getFrameElement(doc);
if (frame) {
this._unmonitorIntersections(frame.ownerDocument);
}

@@ -324,2 +393,17 @@ }

/**
* Stops polling for intersection changes.
* @param {!Document} doc
* @private
*/
IntersectionObserver.prototype._unmonitorAllIntersections = function() {
var unsubscribes = this._monitoringUnsubscribes.slice(0);
this._monitoringDocuments.length = 0;
this._monitoringUnsubscribes.length = 0;
for (var i = 0; i < unsubscribes.length; i++) {
unsubscribes[i]();
}
};
/**
* Scans each observation target for intersection changes and adds them

@@ -340,3 +424,3 @@ * to the internal entries queue. If new entries are found, it

var intersectionRect = rootIsInDom && rootContainsTarget &&
this._computeTargetAndRootIntersection(target, rootRect);
this._computeTargetAndRootIntersection(target, targetRect, rootRect);

@@ -381,2 +465,3 @@ var newEntry = item.entry = new IntersectionObserverEntry({

* @param {Element} target The target DOM element
* @param {Object} targetRect The bounding rect of the target.
* @param {Object} rootRect The bounding rect of the root after being

@@ -389,8 +474,6 @@ * expanded by the rootMargin value.

IntersectionObserver.prototype._computeTargetAndRootIntersection =
function(target, rootRect) {
function(target, targetRect, rootRect) {
// 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;

@@ -400,3 +483,3 @@ var parent = getParentNode(target);

while (!atRoot) {
while (!atRoot && parent) {
var parentRect = null;

@@ -407,7 +490,23 @@ var parentComputedStyle = parent.nodeType == 1 ?

// If the parent isn't displayed, an intersection can't happen.
if (parentComputedStyle.display == 'none') return;
if (parentComputedStyle.display == 'none') return null;
if (parent == this.root || parent == document) {
if (parent == this.root || parent.nodeType == /* DOCUMENT */ 9) {
atRoot = true;
parentRect = rootRect;
if (parent == this.root || parent == document) {
parentRect = rootRect;
} else {
// Check if there's a frame that can be navigated to.
var frame = getParentNode(parent);
var frameRect = frame && getBoundingClientRect(frame);
var frameIntersect =
frame &&
this._computeTargetAndRootIntersection(frame, frameRect, rootRect);
if (frameRect && frameIntersect) {
parent = frame;
parentRect = convertFromParentRect(frameRect, frameIntersect);
} else {
parent = null;
intersectionRect = null;
}
}
} else {

@@ -418,4 +517,5 @@ // If the element has a non-visible overflow, and it's not the <body>

// the document rect, so no need to compute a new intersection.
if (parent != document.body &&
parent != document.documentElement &&
var doc = parent.ownerDocument;
if (parent != doc.body &&
parent != doc.documentElement &&
parentComputedStyle.overflow != 'visible') {

@@ -430,6 +530,5 @@ parentRect = getBoundingClientRect(parent);

intersectionRect = computeRectIntersection(parentRect, intersectionRect);
if (!intersectionRect) break;
}
parent = getParentNode(parent);
if (!intersectionRect) break;
parent = parent && getParentNode(parent);
}

@@ -543,3 +642,4 @@ return intersectionRect;

IntersectionObserver.prototype._rootContainsTarget = function(target) {
return containsDeep(this.root || document, target);
return containsDeep(this.root || document, target) &&
(!this.root || this.root.ownerDocument == target.ownerDocument);
};

@@ -659,3 +759,3 @@

height: height
};
} || null;
}

@@ -712,3 +812,25 @@

/**
* Inverts the intersection and bounding rect from the parent (frame) BCR to
* the local BCR space.
* @param {Object} parentBoundingRect The parent's bound client rect.
* @param {Object} parentIntersectionRect The parent's own intersection rect.
* @return {Object} The local root bounding rect for the parent's children.
*/
function convertFromParentRect(parentBoundingRect, parentIntersectionRect) {
var top = parentIntersectionRect.top - parentBoundingRect.top;
var left = parentIntersectionRect.left - parentBoundingRect.left;
return {
top: top,
left: left,
height: parentIntersectionRect.height,
width: parentIntersectionRect.width,
bottom: top + parentIntersectionRect.height,
right: left + parentIntersectionRect.width
};
}
/**
* Checks to see if a parent element contains a child element (including inside

@@ -740,2 +862,7 @@ * shadow DOM).

if (node.nodeType == /* DOCUMENT */ 9 && node != document) {
// If this node is a document node, look for the embedding frame.
return getFrameElement(node);
}
if (parent && parent.nodeType == 11 && parent.host) {

@@ -755,2 +882,17 @@ // If the parent is a shadow root, return the host element.

/**
* Returns the embedding frame element, if any.
* @param {!Document} doc
* @return {!Element}
*/
function getFrameElement(doc) {
try {
return doc.defaultView && doc.defaultView.frameElement || null;
} catch (e) {
// Ignore the error.
return null;
}
}
// Exposes the constructors globally.

@@ -757,0 +899,0 @@ window.IntersectionObserver = IntersectionObserver;

{
"name": "intersection-observer",
"version": "0.7.0",
"version": "0.8.0",
"description": "A polyfill for IntersectionObserver",

@@ -5,0 +5,0 @@ "main": "intersection-observer",

@@ -101,6 +101,4 @@ # `IntersectionObserver` polyfill

This polyfill does not attempt to report intersections across same-origin `iframe` boundaries. While supporting same-origin iframes is technically possible, it would drastically reduce performance. Since most `iframe` usage on the web is cross-origin, this polyfill chooses to optimize for performantly handling the most common use cases. (Note: neither this polyfill nor native implementations support reporting intersections across cross-origin `iframe` boundaries.)
This polyfill does not support the [proposed v2 additions](https://github.com/szager-chromium/IntersectionObserver/blob/v2/explainer.md), as these features are not currently possible to do with JavaScript and existing web APIs.
This polyfill also does not support the [proposed v2 additions](https://github.com/szager-chromium/IntersectionObserver/blob/v2/explainer.md), as these features are not currently possible to do with JavaScript and existing web APIs.
## Browser support

@@ -110,6 +108,6 @@

Legacy support is also possible in very old browsers by including a shim for ES5 as well as the `window.getComputedStyle` method. The easiest way to load the IntersectionObserver polyfill and have it work in the widest range of browsers is via [polyfill.io](https://cdn.polyfill.io/v2/docs/), which will automatically include dependencies where necessary:
Legacy support is also possible in very old browsers by including a shim for ES5 as well as the `window.getComputedStyle` method. The easiest way to load the IntersectionObserver polyfill and have it work in the widest range of browsers is via [polyfill.io](https://cdn.polyfill.io/v3/), which will automatically include dependencies where necessary:
```html
<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
<script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver"></script>
```

@@ -116,0 +114,0 @@

Sorry, the diff of this file is not supported yet