intersection-observer 0.10.0 → 0.12.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +6 -6
- package/intersection-observer-test.js +906 -103
- package/intersection-observer.js +64 -31
- package/package.json +2 -2
package/README.md
CHANGED
@@ -61,16 +61,16 @@ There are, however, additional use cases that the default configuration will not
|
|
61
61
|
|
62
62
|
If you need to handle any of these use-cases, you can configure the polyfill to poll the document by setting the `POLL_INTERVAL` property. This can be set either globally or on a per-instance basis.
|
63
63
|
|
64
|
-
**Enabling polling for all
|
64
|
+
**Enabling polling for all instances:**
|
65
65
|
|
66
|
-
To enable polling for all
|
66
|
+
To enable polling for all instances, set a value for `POLL_INTERVAL` on the `IntersectionObserver` prototype:
|
67
67
|
|
68
68
|
|
69
69
|
```js
|
70
70
|
IntersectionObserver.prototype.POLL_INTERVAL = 100; // Time in milliseconds.
|
71
71
|
```
|
72
72
|
|
73
|
-
**Enabling polling for individual
|
73
|
+
**Enabling polling for individual instances:**
|
74
74
|
|
75
75
|
To enable polling on only specific instances, set a `POLL_INTERVAL` value on the instance itself:
|
76
76
|
|
@@ -94,7 +94,7 @@ var io = new IntersectionObserver(callback);
|
|
94
94
|
io.USE_MUTATION_OBSERVER = false;
|
95
95
|
```
|
96
96
|
|
97
|
-
This is recommended in cases where the DOM will update frequently but you know those updates will have no
|
97
|
+
This is recommended in cases where the DOM will update frequently but you know those updates will have no effect on the position or your target elements.
|
98
98
|
|
99
99
|
|
100
100
|
## iframe support
|
@@ -171,7 +171,7 @@ Legacy support is also possible in very old browsers by including a shim for ES5
|
|
171
171
|
<script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver"></script>
|
172
172
|
```
|
173
173
|
|
174
|
-
With these polyfills, `IntersectionObserver` has been tested
|
174
|
+
With these polyfills, `IntersectionObserver` has been tested and known to work in the following browsers:
|
175
175
|
|
176
176
|
<table>
|
177
177
|
<tr>
|
@@ -210,4 +210,4 @@ With these polyfills, `IntersectionObserver` has been tested an known to work in
|
|
210
210
|
|
211
211
|
To run the test suite for the `IntersectionObserver` polyfill, open the [`intersection-observer-test.html`](./intersection-observer-test.html) page in the browser of your choice.
|
212
212
|
|
213
|
-
If you run the tests in a browser that
|
213
|
+
If you run the tests in a browser that supports `IntersectionObserver` natively, the tests will be run against the native implementation. If it doesn't, the tests will be run against the polyfill.
|
@@ -69,12 +69,15 @@ describe('IntersectionObserver', function() {
|
|
69
69
|
io = new IntersectionObserver(noop);
|
70
70
|
expect(io.root).to.be(null);
|
71
71
|
|
72
|
+
io = new IntersectionObserver(noop, {root: document});
|
73
|
+
expect(io.root).to.be(document);
|
74
|
+
|
72
75
|
io = new IntersectionObserver(noop, {root: rootEl});
|
73
76
|
expect(io.root).to.be(rootEl);
|
74
77
|
});
|
75
78
|
|
76
79
|
|
77
|
-
it('throws when root is not
|
80
|
+
it('throws when root is not a Document or Element', function() {
|
78
81
|
expect(function() {
|
79
82
|
io = new IntersectionObserver(noop, {root: 'foo'});
|
80
83
|
}).to.throwException();
|
@@ -958,6 +961,7 @@ describe('IntersectionObserver', function() {
|
|
958
961
|
|
959
962
|
describe('iframe', function() {
|
960
963
|
var iframe;
|
964
|
+
var iframeWin, iframeDoc;
|
961
965
|
var documentElement, body;
|
962
966
|
var iframeTargetEl1, iframeTargetEl2;
|
963
967
|
var bodyWidth;
|
@@ -1039,7 +1043,7 @@ describe('IntersectionObserver', function() {
|
|
1039
1043
|
});
|
1040
1044
|
}
|
1041
1045
|
|
1042
|
-
describe('same-origin iframe', function() {
|
1046
|
+
describe('same-origin iframe loaded in the mainframe', function() {
|
1043
1047
|
it('iframe targets do not intersect with a top root element', function(done) {
|
1044
1048
|
var io = new IntersectionObserver(function(unsortedRecords) {
|
1045
1049
|
var records = sortRecords(unsortedRecords);
|
@@ -1397,6 +1401,89 @@ describe('IntersectionObserver', function() {
|
|
1397
1401
|
io.observe(iframeTargetEl2);
|
1398
1402
|
});
|
1399
1403
|
|
1404
|
+
it('handles tracking iframe viewport', function(done) {
|
1405
|
+
iframe.style.height = '100px';
|
1406
|
+
iframe.style.top = '100px';
|
1407
|
+
iframeWin.scrollTo(0, 110);
|
1408
|
+
// {root:iframeDoc} means to track the iframe viewport.
|
1409
|
+
var io = new IntersectionObserver(
|
1410
|
+
function (records) {
|
1411
|
+
io.unobserve(iframeTargetEl1);
|
1412
|
+
|
1413
|
+
var intersectionRect = rect({
|
1414
|
+
top: 0, // if root=null, then this would be 100.
|
1415
|
+
left: 0,
|
1416
|
+
height: 90,
|
1417
|
+
width: bodyWidth
|
1418
|
+
});
|
1419
|
+
expect(records.length).to.be(1);
|
1420
|
+
expect(rect(records[0].rootBounds)).to.eql(getRootRect(iframeDoc));
|
1421
|
+
expect(rect(records[0].intersectionRect)).to.eql(intersectionRect);
|
1422
|
+
done();
|
1423
|
+
},
|
1424
|
+
{ root: iframeDoc }
|
1425
|
+
);
|
1426
|
+
|
1427
|
+
io.observe(iframeTargetEl1);
|
1428
|
+
});
|
1429
|
+
|
1430
|
+
it('handles tracking iframe viewport with rootMargin', function(done) {
|
1431
|
+
iframe.style.height = '100px';
|
1432
|
+
|
1433
|
+
var io = new IntersectionObserver(
|
1434
|
+
function (records) {
|
1435
|
+
io.unobserve(iframeTargetEl1);
|
1436
|
+
var intersectionRect = rect({
|
1437
|
+
top: 0, // if root=null, then this would be 100.
|
1438
|
+
left: 0,
|
1439
|
+
height: 200,
|
1440
|
+
width: bodyWidth
|
1441
|
+
});
|
1442
|
+
|
1443
|
+
// rootMargin: 100% --> 3x width + 3x height.
|
1444
|
+
var expectedRootBounds = rect({
|
1445
|
+
top: -100,
|
1446
|
+
left: -bodyWidth,
|
1447
|
+
width: bodyWidth * 3,
|
1448
|
+
height: 100 * 3
|
1449
|
+
});
|
1450
|
+
expect(records.length).to.be(1);
|
1451
|
+
expect(rect(records[0].rootBounds)).to.eql(expectedRootBounds);
|
1452
|
+
expect(rect(records[0].intersectionRect)).to.eql(intersectionRect);
|
1453
|
+
done();
|
1454
|
+
},
|
1455
|
+
{ root: iframeDoc, rootMargin: '100%' }
|
1456
|
+
);
|
1457
|
+
|
1458
|
+
io.observe(iframeTargetEl1);
|
1459
|
+
});
|
1460
|
+
|
1461
|
+
// Current spec indicates that cross-document tracking yields
|
1462
|
+
// an essentially empty IntersectionObserverEntry.
|
1463
|
+
// See: https://github.com/w3c/IntersectionObserver/issues/87
|
1464
|
+
it('does not track cross-document elements', function(done) {
|
1465
|
+
var io = new IntersectionObserver(
|
1466
|
+
function (records) {
|
1467
|
+
io.unobserve(iframeTargetEl1)
|
1468
|
+
|
1469
|
+
expect(records.length).to.be(1);
|
1470
|
+
const zeroesRect = rect({
|
1471
|
+
top: 0,
|
1472
|
+
left: 0,
|
1473
|
+
width: 0,
|
1474
|
+
height: 0
|
1475
|
+
});
|
1476
|
+
expect(rect(records[0].rootBounds)).to.eql(zeroesRect);
|
1477
|
+
expect(rect(records[0].intersectionRect)).to.eql(zeroesRect);
|
1478
|
+
expect(records.isIntersecting).false;
|
1479
|
+
done();
|
1480
|
+
},
|
1481
|
+
{ root: document }
|
1482
|
+
);
|
1483
|
+
|
1484
|
+
io.observe(iframeTargetEl1);
|
1485
|
+
});
|
1486
|
+
|
1400
1487
|
it('handles style changes', function(done) {
|
1401
1488
|
var spy = sinon.spy();
|
1402
1489
|
|
@@ -1619,13 +1706,10 @@ describe('IntersectionObserver', function() {
|
|
1619
1706
|
});
|
1620
1707
|
});
|
1621
1708
|
|
1622
|
-
describe('
|
1709
|
+
describe('same-origin iframe loaded in an iframe', function() {
|
1623
1710
|
var ASYNC_TIMEOUT = 300;
|
1624
|
-
var crossOriginUpdater;
|
1625
1711
|
|
1626
1712
|
beforeEach(function(done) {
|
1627
|
-
Object.defineProperty(iframeWin, 'frameElement', {value: null});
|
1628
|
-
|
1629
1713
|
/* Uncomment these lines to force polyfill inside the iframe.
|
1630
1714
|
delete iframeWin.IntersectionObserver;
|
1631
1715
|
delete iframeWin.IntersectionObserverEntry;
|
@@ -1636,9 +1720,6 @@ describe('IntersectionObserver', function() {
|
|
1636
1720
|
var script = iframeDoc.createElement('script');
|
1637
1721
|
script.src = 'intersection-observer.js';
|
1638
1722
|
script.onload = function() {
|
1639
|
-
if (iframeWin.IntersectionObserver._setupCrossOriginUpdater) {
|
1640
|
-
crossOriginUpdater = iframeWin.IntersectionObserver._setupCrossOriginUpdater();
|
1641
|
-
}
|
1642
1723
|
done();
|
1643
1724
|
};
|
1644
1725
|
iframeDoc.body.appendChild(script);
|
@@ -1647,12 +1728,6 @@ describe('IntersectionObserver', function() {
|
|
1647
1728
|
}
|
1648
1729
|
});
|
1649
1730
|
|
1650
|
-
afterEach(function() {
|
1651
|
-
if (IntersectionObserver._resetCrossOriginUpdater) {
|
1652
|
-
IntersectionObserver._resetCrossOriginUpdater();
|
1653
|
-
}
|
1654
|
-
});
|
1655
|
-
|
1656
1731
|
function computeRectIntersection(rect1, rect2) {
|
1657
1732
|
var top = Math.max(rect1.top, rect2.top);
|
1658
1733
|
var bottom = Math.min(rect1.bottom, rect2.bottom);
|
@@ -1678,25 +1753,19 @@ describe('IntersectionObserver', function() {
|
|
1678
1753
|
};
|
1679
1754
|
}
|
1680
1755
|
|
1681
|
-
function
|
1756
|
+
function checkRootBounds(records) {
|
1682
1757
|
if (!supportsNativeIntersectionObserver(iframeWin)) {
|
1683
1758
|
records.forEach(function(record) {
|
1684
|
-
expect(record.rootBounds).to.
|
1759
|
+
expect(rect(record.rootBounds)).to.eql(getRootRect(document));
|
1685
1760
|
});
|
1686
1761
|
}
|
1687
1762
|
}
|
1688
1763
|
|
1689
1764
|
function applyParentRect(parentRect) {
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
} else {
|
1695
|
-
iframe.style.top = parentRect.top + 'px';
|
1696
|
-
iframe.style.left = parentRect.left + 'px';
|
1697
|
-
iframe.style.height = parentRect.height + 'px';
|
1698
|
-
iframe.style.width = parentRect.width + 'px';
|
1699
|
-
}
|
1765
|
+
iframe.style.top = parentRect.top + 'px';
|
1766
|
+
iframe.style.left = parentRect.left + 'px';
|
1767
|
+
iframe.style.height = parentRect.height + 'px';
|
1768
|
+
iframe.style.width = parentRect.width + 'px';
|
1700
1769
|
}
|
1701
1770
|
|
1702
1771
|
function createObserver(callback, options, parentRect) {
|
@@ -1712,7 +1781,7 @@ describe('IntersectionObserver', function() {
|
|
1712
1781
|
var io = createObserver(function(unsortedRecords) {
|
1713
1782
|
var records = sortRecords(unsortedRecords);
|
1714
1783
|
expect(records.length).to.be(3);
|
1715
|
-
|
1784
|
+
checkRootBounds(records);
|
1716
1785
|
|
1717
1786
|
// The documentElement is partially visible.
|
1718
1787
|
expect(rect(records[0].boundingClientRect))
|
@@ -1765,7 +1834,7 @@ describe('IntersectionObserver', function() {
|
|
1765
1834
|
var io = createObserver(function(unsortedRecords) {
|
1766
1835
|
var records = sortRecords(unsortedRecords);
|
1767
1836
|
expect(records.length).to.be(3);
|
1768
|
-
|
1837
|
+
checkRootBounds(records);
|
1769
1838
|
|
1770
1839
|
// The documentElement is partially visible.
|
1771
1840
|
expect(rect(records[0].boundingClientRect))
|
@@ -1818,7 +1887,7 @@ describe('IntersectionObserver', function() {
|
|
1818
1887
|
var io = createObserver(function(unsortedRecords) {
|
1819
1888
|
var records = sortRecords(unsortedRecords);
|
1820
1889
|
expect(records.length).to.be(3);
|
1821
|
-
|
1890
|
+
checkRootBounds(records);
|
1822
1891
|
|
1823
1892
|
// The documentElement is partially visible.
|
1824
1893
|
expect(rect(records[0].boundingClientRect))
|
@@ -1879,7 +1948,7 @@ describe('IntersectionObserver', function() {
|
|
1879
1948
|
var io = createObserver(function(unsortedRecords) {
|
1880
1949
|
var records = sortRecords(unsortedRecords);
|
1881
1950
|
expect(records.length).to.be(3);
|
1882
|
-
|
1951
|
+
checkRootBounds(records);
|
1883
1952
|
|
1884
1953
|
// The documentElement is partially visible.
|
1885
1954
|
expect(rect(records[0].boundingClientRect))
|
@@ -1933,7 +2002,7 @@ describe('IntersectionObserver', function() {
|
|
1933
2002
|
var io = createObserver(function(unsortedRecords) {
|
1934
2003
|
var records = sortRecords(unsortedRecords);
|
1935
2004
|
expect(records.length).to.be(3);
|
1936
|
-
|
2005
|
+
checkRootBounds(records);
|
1937
2006
|
|
1938
2007
|
// The documentElement is partially visible.
|
1939
2008
|
expect(rect(records[0].boundingClientRect))
|
@@ -1994,7 +2063,7 @@ describe('IntersectionObserver', function() {
|
|
1994
2063
|
var io = createObserver(function(unsortedRecords) {
|
1995
2064
|
var records = sortRecords(unsortedRecords);
|
1996
2065
|
expect(records.length).to.be(4);
|
1997
|
-
|
2066
|
+
checkRootBounds(records);
|
1998
2067
|
|
1999
2068
|
// The documentElement is partially visible.
|
2000
2069
|
expect(rect(records[0].boundingClientRect))
|
@@ -2075,7 +2144,7 @@ describe('IntersectionObserver', function() {
|
|
2075
2144
|
var io = createObserver(function(unsortedRecords) {
|
2076
2145
|
var records = sortRecords(unsortedRecords);
|
2077
2146
|
expect(records.length).to.be(3);
|
2078
|
-
|
2147
|
+
checkRootBounds(records);
|
2079
2148
|
|
2080
2149
|
var emptyRect = rect({
|
2081
2150
|
top: 0,
|
@@ -2118,72 +2187,6 @@ describe('IntersectionObserver', function() {
|
|
2118
2187
|
io.observe(iframeTargetEl1);
|
2119
2188
|
});
|
2120
2189
|
|
2121
|
-
it('blocks until crossOriginUpdater is called first time', function(done) {
|
2122
|
-
if (supportsNativeIntersectionObserver(iframeWin)) {
|
2123
|
-
// Skip: not possible to emulate with the native observer.
|
2124
|
-
done();
|
2125
|
-
return;
|
2126
|
-
}
|
2127
|
-
|
2128
|
-
var spy = sinon.spy();
|
2129
|
-
|
2130
|
-
var parentRect = rect({top: 0, left: 20, height: 300, width: 100});
|
2131
|
-
|
2132
|
-
var io = createObserver(spy, {});
|
2133
|
-
io.observe(iframeTargetEl1);
|
2134
|
-
|
2135
|
-
runSequence([
|
2136
|
-
function(done) {
|
2137
|
-
setTimeout(function() {
|
2138
|
-
expect(spy.callCount).to.be(0);
|
2139
|
-
|
2140
|
-
// Issue the first update.
|
2141
|
-
crossOriginUpdater(parentRect, null);
|
2142
|
-
|
2143
|
-
done();
|
2144
|
-
}, ASYNC_TIMEOUT);
|
2145
|
-
},
|
2146
|
-
function(done) {
|
2147
|
-
setTimeout(function() {
|
2148
|
-
expect(spy.callCount).to.be(1);
|
2149
|
-
var records = sortRecords(spy.lastCall.args[0]);
|
2150
|
-
expect(records.length).to.be(1);
|
2151
|
-
expect(records[0].intersectionRatio).to.be(0);
|
2152
|
-
expect(records[0].isIntersecting).to.be(false);
|
2153
|
-
done();
|
2154
|
-
}, ASYNC_TIMEOUT);
|
2155
|
-
},
|
2156
|
-
function(done) {
|
2157
|
-
io.disconnect();
|
2158
|
-
done();
|
2159
|
-
}
|
2160
|
-
], done);
|
2161
|
-
});
|
2162
|
-
|
2163
|
-
it('doesn\'t block with a root specified', function(done) {
|
2164
|
-
var spy = sinon.spy();
|
2165
|
-
|
2166
|
-
var io = createObserver(spy, {root: body});
|
2167
|
-
io.observe(iframeTargetEl1);
|
2168
|
-
|
2169
|
-
runSequence([
|
2170
|
-
function(done) {
|
2171
|
-
setTimeout(function() {
|
2172
|
-
expect(spy.callCount).to.be(1);
|
2173
|
-
var record = sortRecords(spy.lastCall.args[0])[0];
|
2174
|
-
expect(record.intersectionRatio).to.be(1);
|
2175
|
-
expect(record.isIntersecting).to.be(true);
|
2176
|
-
expect(rect(record.rootBounds)).to.eql(rect(body.getBoundingClientRect()));
|
2177
|
-
done();
|
2178
|
-
}, ASYNC_TIMEOUT);
|
2179
|
-
},
|
2180
|
-
function(done) {
|
2181
|
-
io.disconnect();
|
2182
|
-
done();
|
2183
|
-
}
|
2184
|
-
], done);
|
2185
|
-
});
|
2186
|
-
|
2187
2190
|
it('handles style changes', function(done) {
|
2188
2191
|
var spy = sinon.spy();
|
2189
2192
|
|
@@ -2315,7 +2318,7 @@ describe('IntersectionObserver', function() {
|
|
2315
2318
|
expect(spy.callCount).to.be(1);
|
2316
2319
|
var records = sortRecords(spy.lastCall.args[0]);
|
2317
2320
|
expect(records.length).to.be(2);
|
2318
|
-
|
2321
|
+
checkRootBounds(records);
|
2319
2322
|
expect(records[0].intersectionRatio).to.be(1);
|
2320
2323
|
expect(records[0].isIntersecting).to.be(true);
|
2321
2324
|
expect(records[1].intersectionRatio).to.be(0);
|
@@ -2332,7 +2335,7 @@ describe('IntersectionObserver', function() {
|
|
2332
2335
|
expect(spy.callCount).to.be(2);
|
2333
2336
|
var records = sortRecords(spy.lastCall.args[0]);
|
2334
2337
|
expect(records.length).to.be(1);
|
2335
|
-
|
2338
|
+
checkRootBounds(records);
|
2336
2339
|
expect(records[0].intersectionRatio).to.be(0);
|
2337
2340
|
expect(records[0].isIntersecting).to.be(false);
|
2338
2341
|
// Top-level bounds.
|
@@ -2347,7 +2350,7 @@ describe('IntersectionObserver', function() {
|
|
2347
2350
|
expect(spy.callCount).to.be(3);
|
2348
2351
|
var records = sortRecords(spy.lastCall.args[0]);
|
2349
2352
|
expect(records.length).to.be(1);
|
2350
|
-
|
2353
|
+
checkRootBounds(records);
|
2351
2354
|
expect(records[0].intersectionRatio).to.be.within(0.45, 0.55);
|
2352
2355
|
expect(records[0].isIntersecting).to.be(true);
|
2353
2356
|
// Top-level bounds.
|
@@ -2362,6 +2365,806 @@ describe('IntersectionObserver', function() {
|
|
2362
2365
|
], done);
|
2363
2366
|
});
|
2364
2367
|
});
|
2368
|
+
|
2369
|
+
describe('cross-origin iframe', function() {
|
2370
|
+
var ASYNC_TIMEOUT = 300;
|
2371
|
+
var crossOriginUpdater;
|
2372
|
+
|
2373
|
+
beforeEach(function(done) {
|
2374
|
+
Object.defineProperty(iframeWin, 'frameElement', {value: null});
|
2375
|
+
|
2376
|
+
/* Uncomment these lines to force polyfill inside the iframe.
|
2377
|
+
delete iframeWin.IntersectionObserver;
|
2378
|
+
delete iframeWin.IntersectionObserverEntry;
|
2379
|
+
*/
|
2380
|
+
|
2381
|
+
// Install polyfill right into the iframe.
|
2382
|
+
if (!iframeWin.IntersectionObserver) {
|
2383
|
+
var script = iframeDoc.createElement('script');
|
2384
|
+
script.src = 'intersection-observer.js';
|
2385
|
+
script.onload = function() {
|
2386
|
+
if (iframeWin.IntersectionObserver._setupCrossOriginUpdater) {
|
2387
|
+
crossOriginUpdater = iframeWin.IntersectionObserver._setupCrossOriginUpdater();
|
2388
|
+
}
|
2389
|
+
done();
|
2390
|
+
};
|
2391
|
+
iframeDoc.body.appendChild(script);
|
2392
|
+
} else {
|
2393
|
+
done();
|
2394
|
+
}
|
2395
|
+
});
|
2396
|
+
|
2397
|
+
afterEach(function() {
|
2398
|
+
if (IntersectionObserver._resetCrossOriginUpdater) {
|
2399
|
+
IntersectionObserver._resetCrossOriginUpdater();
|
2400
|
+
}
|
2401
|
+
});
|
2402
|
+
|
2403
|
+
function computeRectIntersection(rect1, rect2) {
|
2404
|
+
var top = Math.max(rect1.top, rect2.top);
|
2405
|
+
var bottom = Math.min(rect1.bottom, rect2.bottom);
|
2406
|
+
var left = Math.max(rect1.left, rect2.left);
|
2407
|
+
var right = Math.min(rect1.right, rect2.right);
|
2408
|
+
var width = right - left;
|
2409
|
+
var height = bottom - top;
|
2410
|
+
|
2411
|
+
return (width >= 0 && height >= 0) && {
|
2412
|
+
top: top,
|
2413
|
+
bottom: bottom,
|
2414
|
+
left: left,
|
2415
|
+
right: right,
|
2416
|
+
width: width,
|
2417
|
+
height: height
|
2418
|
+
} || {
|
2419
|
+
top: 0,
|
2420
|
+
bottom: 0,
|
2421
|
+
left: 0,
|
2422
|
+
right: 0,
|
2423
|
+
width: 0,
|
2424
|
+
height: 0
|
2425
|
+
};
|
2426
|
+
}
|
2427
|
+
|
2428
|
+
function checkRootBoundsAreNull(records) {
|
2429
|
+
if (!supportsNativeIntersectionObserver(iframeWin)) {
|
2430
|
+
records.forEach(function(record) {
|
2431
|
+
expect(record.rootBounds).to.be(null);
|
2432
|
+
});
|
2433
|
+
}
|
2434
|
+
}
|
2435
|
+
|
2436
|
+
function applyParentRect(parentRect) {
|
2437
|
+
if (crossOriginUpdater) {
|
2438
|
+
var parentIntersectionRect = computeRectIntersection(
|
2439
|
+
parentRect, getRootRect(document));
|
2440
|
+
crossOriginUpdater(parentRect, parentIntersectionRect);
|
2441
|
+
} else {
|
2442
|
+
iframe.style.top = parentRect.top + 'px';
|
2443
|
+
iframe.style.left = parentRect.left + 'px';
|
2444
|
+
iframe.style.height = parentRect.height + 'px';
|
2445
|
+
iframe.style.width = parentRect.width + 'px';
|
2446
|
+
}
|
2447
|
+
}
|
2448
|
+
|
2449
|
+
function createObserver(callback, options, parentRect) {
|
2450
|
+
var io = new iframeWin.IntersectionObserver(callback, options);
|
2451
|
+
if (parentRect) {
|
2452
|
+
applyParentRect(parentRect);
|
2453
|
+
}
|
2454
|
+
return io;
|
2455
|
+
}
|
2456
|
+
|
2457
|
+
it('calculates rects for a fully visible frame', function(done) {
|
2458
|
+
var parentRect = rect({top: 0, left: 20, height: 300, width: 100});
|
2459
|
+
var io = createObserver(function(unsortedRecords) {
|
2460
|
+
var records = sortRecords(unsortedRecords);
|
2461
|
+
expect(records.length).to.be(3);
|
2462
|
+
checkRootBoundsAreNull(records);
|
2463
|
+
|
2464
|
+
// The documentElement is partially visible.
|
2465
|
+
expect(rect(records[0].boundingClientRect))
|
2466
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
2467
|
+
expect(rect(records[0].intersectionRect)).to.eql(rect({
|
2468
|
+
top: 0,
|
2469
|
+
left: 0,
|
2470
|
+
width: bodyWidth,
|
2471
|
+
height: 300
|
2472
|
+
}));
|
2473
|
+
expect(records[0].isIntersecting).to.be(true);
|
2474
|
+
// 300 / 404 == ~0.743
|
2475
|
+
expect(records[0].intersectionRatio).to.be.within(0.74, 0.75);
|
2476
|
+
|
2477
|
+
// The document.body is partially visible.
|
2478
|
+
expect(rect(records[1].boundingClientRect))
|
2479
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
2480
|
+
expect(rect(records[1].intersectionRect)).to.eql(rect({
|
2481
|
+
top: 0,
|
2482
|
+
left: 0,
|
2483
|
+
width: bodyWidth,
|
2484
|
+
height: 300
|
2485
|
+
}));
|
2486
|
+
expect(records[1].isIntersecting).to.be(true);
|
2487
|
+
// 300 / 402 == ~0.746
|
2488
|
+
expect(records[1].intersectionRatio).to.be.within(0.74, 0.75);
|
2489
|
+
|
2490
|
+
// The target1 is fully visible.
|
2491
|
+
var clientRect1 = rect({
|
2492
|
+
top: 0,
|
2493
|
+
left: 0,
|
2494
|
+
width: bodyWidth,
|
2495
|
+
height: 200
|
2496
|
+
});
|
2497
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
2498
|
+
expect(rect(records[2].intersectionRect)).to.eql(clientRect1);
|
2499
|
+
expect(records[2].isIntersecting).to.be(true);
|
2500
|
+
expect(records[2].intersectionRatio).to.be(1);
|
2501
|
+
|
2502
|
+
done();
|
2503
|
+
io.disconnect();
|
2504
|
+
}, {}, parentRect);
|
2505
|
+
io.observe(documentElement);
|
2506
|
+
io.observe(body);
|
2507
|
+
io.observe(iframeTargetEl1);
|
2508
|
+
});
|
2509
|
+
|
2510
|
+
it('calculates rects for a fully visible and offset frame', function(done) {
|
2511
|
+
var parentRect = rect({top: 10, left: 20, height: 300, width: 100});
|
2512
|
+
var io = createObserver(function(unsortedRecords) {
|
2513
|
+
var records = sortRecords(unsortedRecords);
|
2514
|
+
expect(records.length).to.be(3);
|
2515
|
+
checkRootBoundsAreNull(records);
|
2516
|
+
|
2517
|
+
// The documentElement is partially visible.
|
2518
|
+
expect(rect(records[0].boundingClientRect))
|
2519
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
2520
|
+
expect(rect(records[0].intersectionRect)).to.eql(rect({
|
2521
|
+
top: 0,
|
2522
|
+
left: 0,
|
2523
|
+
width: bodyWidth,
|
2524
|
+
height: 300
|
2525
|
+
}));
|
2526
|
+
expect(records[0].isIntersecting).to.be(true);
|
2527
|
+
// 300 / 404 == ~0.743
|
2528
|
+
expect(records[0].intersectionRatio).to.be.within(0.74, 0.75);
|
2529
|
+
|
2530
|
+
// The document.body is partially visible.
|
2531
|
+
expect(rect(records[1].boundingClientRect))
|
2532
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
2533
|
+
expect(rect(records[1].intersectionRect)).to.eql(rect({
|
2534
|
+
top: 0,
|
2535
|
+
left: 0,
|
2536
|
+
width: bodyWidth,
|
2537
|
+
height: 300
|
2538
|
+
}));
|
2539
|
+
expect(records[1].isIntersecting).to.be(true);
|
2540
|
+
// 300 / 402 == ~0.746
|
2541
|
+
expect(records[1].intersectionRatio).to.be.within(0.74, 0.75);
|
2542
|
+
|
2543
|
+
// The target1 is fully visible.
|
2544
|
+
var clientRect1 = rect({
|
2545
|
+
top: 0,
|
2546
|
+
left: 0,
|
2547
|
+
width: bodyWidth,
|
2548
|
+
height: 200
|
2549
|
+
});
|
2550
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
2551
|
+
expect(rect(records[2].intersectionRect)).to.eql(clientRect1);
|
2552
|
+
expect(records[2].isIntersecting).to.be(true);
|
2553
|
+
expect(records[2].intersectionRatio).to.be(1);
|
2554
|
+
|
2555
|
+
done();
|
2556
|
+
io.disconnect();
|
2557
|
+
}, {}, parentRect);
|
2558
|
+
io.observe(documentElement);
|
2559
|
+
io.observe(body);
|
2560
|
+
io.observe(iframeTargetEl1);
|
2561
|
+
});
|
2562
|
+
|
2563
|
+
it('calculates rects for a clipped frame on top', function(done) {
|
2564
|
+
var parentRect = rect({top: -10, left: 20, height: 300, width: 100});
|
2565
|
+
var io = createObserver(function(unsortedRecords) {
|
2566
|
+
var records = sortRecords(unsortedRecords);
|
2567
|
+
expect(records.length).to.be(3);
|
2568
|
+
checkRootBoundsAreNull(records);
|
2569
|
+
|
2570
|
+
// The documentElement is partially visible.
|
2571
|
+
expect(rect(records[0].boundingClientRect))
|
2572
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
2573
|
+
expect(rect(records[0].intersectionRect)).to.eql(rect({
|
2574
|
+
top: 10,
|
2575
|
+
left: 0,
|
2576
|
+
width: bodyWidth,
|
2577
|
+
height: 300 - 10
|
2578
|
+
}));
|
2579
|
+
expect(records[0].isIntersecting).to.be(true);
|
2580
|
+
// (300 - 10) / 404 == ~0.717
|
2581
|
+
expect(records[0].intersectionRatio).to.be.within(0.71, 0.72);
|
2582
|
+
|
2583
|
+
// The document.body is partially visible.
|
2584
|
+
expect(rect(records[1].boundingClientRect))
|
2585
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
2586
|
+
expect(rect(records[1].intersectionRect)).to.eql(rect({
|
2587
|
+
top: 10,
|
2588
|
+
left: 0,
|
2589
|
+
width: bodyWidth,
|
2590
|
+
height: 300 - 10
|
2591
|
+
}));
|
2592
|
+
expect(records[1].isIntersecting).to.be(true);
|
2593
|
+
// (300 - 10) / 402 == ~0.721
|
2594
|
+
expect(records[1].intersectionRatio).to.be.within(0.72, 0.73);
|
2595
|
+
|
2596
|
+
// The target1 is clipped at the top by the iframe's clipping.
|
2597
|
+
var clientRect1 = rect({
|
2598
|
+
top: 0,
|
2599
|
+
left: 0,
|
2600
|
+
width: bodyWidth,
|
2601
|
+
height: 200
|
2602
|
+
});
|
2603
|
+
var intersectRect1 = rect({
|
2604
|
+
left: 0,
|
2605
|
+
width: bodyWidth,
|
2606
|
+
// Top is clipped.
|
2607
|
+
top: 10,
|
2608
|
+
height: 200 - 10
|
2609
|
+
});
|
2610
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
2611
|
+
expect(rect(records[2].intersectionRect)).to.eql(intersectRect1);
|
2612
|
+
expect(records[2].isIntersecting).to.be(true);
|
2613
|
+
expect(records[2].intersectionRatio).to.within(0.94, 0.96); // ~0.95
|
2614
|
+
|
2615
|
+
done();
|
2616
|
+
io.disconnect();
|
2617
|
+
}, {}, parentRect);
|
2618
|
+
io.observe(documentElement);
|
2619
|
+
io.observe(body);
|
2620
|
+
io.observe(iframeTargetEl1);
|
2621
|
+
});
|
2622
|
+
|
2623
|
+
it('calculates rects for a clipped frame on bottom', function(done) {
|
2624
|
+
var rootRect = getRootRect(document);
|
2625
|
+
var parentRect = rect({top: rootRect.bottom - 300 + 10, left: 20, height: 300, width: 100});
|
2626
|
+
var io = createObserver(function(unsortedRecords) {
|
2627
|
+
var records = sortRecords(unsortedRecords);
|
2628
|
+
expect(records.length).to.be(3);
|
2629
|
+
checkRootBoundsAreNull(records);
|
2630
|
+
|
2631
|
+
// The documentElement is partially visible.
|
2632
|
+
expect(rect(records[0].boundingClientRect))
|
2633
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
2634
|
+
expect(rect(records[0].intersectionRect)).to.eql(rect({
|
2635
|
+
top: 0,
|
2636
|
+
left: 0,
|
2637
|
+
width: bodyWidth,
|
2638
|
+
height: 300 - 10
|
2639
|
+
}));
|
2640
|
+
expect(records[0].isIntersecting).to.be(true);
|
2641
|
+
// (300 - 10) / 404 == ~0.717
|
2642
|
+
expect(records[0].intersectionRatio).to.be.within(0.71, 0.72);
|
2643
|
+
|
2644
|
+
// The document.body is partially visible.
|
2645
|
+
expect(rect(records[1].boundingClientRect))
|
2646
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
2647
|
+
expect(rect(records[1].intersectionRect)).to.eql(rect({
|
2648
|
+
top: 0,
|
2649
|
+
left: 0,
|
2650
|
+
width: bodyWidth,
|
2651
|
+
height: 300 - 10
|
2652
|
+
}));
|
2653
|
+
expect(records[1].isIntersecting).to.be(true);
|
2654
|
+
// (300 - 10) / 402 == ~0.721
|
2655
|
+
expect(records[1].intersectionRatio).to.be.within(0.72, 0.73);
|
2656
|
+
|
2657
|
+
// The target1 is clipped at the top by the iframe's clipping.
|
2658
|
+
var clientRect1 = rect({
|
2659
|
+
top: 0,
|
2660
|
+
left: 0,
|
2661
|
+
width: bodyWidth,
|
2662
|
+
height: 200
|
2663
|
+
});
|
2664
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
2665
|
+
expect(rect(records[2].intersectionRect)).to.eql(clientRect1);
|
2666
|
+
expect(records[2].isIntersecting).to.be(true);
|
2667
|
+
expect(records[2].intersectionRatio).to.be(1);
|
2668
|
+
|
2669
|
+
done();
|
2670
|
+
io.disconnect();
|
2671
|
+
}, {}, parentRect);
|
2672
|
+
io.observe(documentElement);
|
2673
|
+
io.observe(body);
|
2674
|
+
io.observe(iframeTargetEl1);
|
2675
|
+
});
|
2676
|
+
|
2677
|
+
it('calculates rects for a fully visible and scrolled frame', function(done) {
|
2678
|
+
iframeWin.scrollTo(0, 10);
|
2679
|
+
var parentRect = rect({top: 0, left: 20, height: 300, width: 100});
|
2680
|
+
var io = createObserver(function(unsortedRecords) {
|
2681
|
+
var records = sortRecords(unsortedRecords);
|
2682
|
+
expect(records.length).to.be(3);
|
2683
|
+
checkRootBoundsAreNull(records);
|
2684
|
+
|
2685
|
+
// The documentElement is partially visible.
|
2686
|
+
expect(rect(records[0].boundingClientRect))
|
2687
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
2688
|
+
expect(rect(records[0].intersectionRect)).to.eql(rect({
|
2689
|
+
top: 0,
|
2690
|
+
left: 0,
|
2691
|
+
width: bodyWidth,
|
2692
|
+
height: 300
|
2693
|
+
}));
|
2694
|
+
expect(records[0].isIntersecting).to.be(true);
|
2695
|
+
// 300 / 404 == ~0.743
|
2696
|
+
expect(records[0].intersectionRatio).to.be.within(0.74, 0.75);
|
2697
|
+
|
2698
|
+
// The document.body is partially visible.
|
2699
|
+
expect(rect(records[1].boundingClientRect))
|
2700
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
2701
|
+
expect(rect(records[1].intersectionRect)).to.eql(rect({
|
2702
|
+
top: 0,
|
2703
|
+
left: 0,
|
2704
|
+
width: bodyWidth,
|
2705
|
+
height: 300
|
2706
|
+
}));
|
2707
|
+
expect(records[1].isIntersecting).to.be(true);
|
2708
|
+
// 300 / 402 == ~0.746
|
2709
|
+
expect(records[1].intersectionRatio).to.be.within(0.74, 0.75);
|
2710
|
+
|
2711
|
+
// The target1 is fully visible.
|
2712
|
+
var clientRect1 = rect({
|
2713
|
+
top: -10,
|
2714
|
+
left: 0,
|
2715
|
+
width: bodyWidth,
|
2716
|
+
height: 200
|
2717
|
+
});
|
2718
|
+
var intersectRect1 = rect({
|
2719
|
+
top: 0,
|
2720
|
+
left: 0,
|
2721
|
+
width: bodyWidth,
|
2722
|
+
// Height is only for the visible area.
|
2723
|
+
height: 200 - 10
|
2724
|
+
});
|
2725
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
2726
|
+
expect(rect(records[2].intersectionRect)).to.eql(intersectRect1);
|
2727
|
+
expect(records[2].isIntersecting).to.be(true);
|
2728
|
+
expect(records[2].intersectionRatio).to.within(0.94, 0.96); // ~0.95
|
2729
|
+
|
2730
|
+
done();
|
2731
|
+
io.disconnect();
|
2732
|
+
}, {}, parentRect);
|
2733
|
+
io.observe(documentElement);
|
2734
|
+
io.observe(body);
|
2735
|
+
io.observe(iframeTargetEl1);
|
2736
|
+
});
|
2737
|
+
|
2738
|
+
it('calculates rects for a clipped frame on top and scrolled', function(done) {
|
2739
|
+
iframeWin.scrollTo(0, 10);
|
2740
|
+
var parentRect = rect({top: -10, left: 0, height: 300, width: 100});
|
2741
|
+
var io = createObserver(function(unsortedRecords) {
|
2742
|
+
var records = sortRecords(unsortedRecords);
|
2743
|
+
expect(records.length).to.be(4);
|
2744
|
+
checkRootBoundsAreNull(records);
|
2745
|
+
|
2746
|
+
// The documentElement is partially visible.
|
2747
|
+
expect(rect(records[0].boundingClientRect))
|
2748
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
2749
|
+
expect(rect(records[0].intersectionRect)).to.eql(rect({
|
2750
|
+
top: 10,
|
2751
|
+
left: 0,
|
2752
|
+
width: bodyWidth,
|
2753
|
+
height: 300 - 10
|
2754
|
+
}));
|
2755
|
+
expect(records[0].isIntersecting).to.be(true);
|
2756
|
+
// (300 - 10) / 404 == ~0.717
|
2757
|
+
expect(records[0].intersectionRatio).to.be.within(0.71, 0.72);
|
2758
|
+
|
2759
|
+
// The document.body is partially visible.
|
2760
|
+
expect(rect(records[1].boundingClientRect))
|
2761
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
2762
|
+
expect(rect(records[1].intersectionRect)).to.eql(rect({
|
2763
|
+
top: 10,
|
2764
|
+
left: 0,
|
2765
|
+
width: bodyWidth,
|
2766
|
+
height: 300 - 10
|
2767
|
+
}));
|
2768
|
+
expect(records[1].isIntersecting).to.be(true);
|
2769
|
+
// (300 - 10) / 402 == ~0.721
|
2770
|
+
expect(records[1].intersectionRatio).to.be.within(0.72, 0.73);
|
2771
|
+
|
2772
|
+
// The target1 is clipped at the top by the iframe's clipping.
|
2773
|
+
var clientRect1 = rect({
|
2774
|
+
top: -10,
|
2775
|
+
left: 0,
|
2776
|
+
width: bodyWidth,
|
2777
|
+
height: 200
|
2778
|
+
});
|
2779
|
+
var intersectRect1 = rect({
|
2780
|
+
left: 0,
|
2781
|
+
width: bodyWidth,
|
2782
|
+
// Top is clipped.
|
2783
|
+
top: 10,
|
2784
|
+
// The height is less by both: offset and scroll.
|
2785
|
+
height: 200 - 10 - 10
|
2786
|
+
});
|
2787
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
2788
|
+
expect(rect(records[2].intersectionRect)).to.eql(intersectRect1);
|
2789
|
+
expect(records[2].isIntersecting).to.be(true);
|
2790
|
+
expect(records[2].intersectionRatio).to.within(0.89, 0.91); // ~0.9
|
2791
|
+
|
2792
|
+
// The target2 is partially visible.
|
2793
|
+
var clientRect2 = rect({
|
2794
|
+
top: 202 - 10,
|
2795
|
+
left: 0,
|
2796
|
+
width: bodyWidth,
|
2797
|
+
height: 200
|
2798
|
+
});
|
2799
|
+
var intersectRect2 = rect({
|
2800
|
+
top: 202 - 10,
|
2801
|
+
left: 0,
|
2802
|
+
width: bodyWidth,
|
2803
|
+
// The bottom is clipped off.
|
2804
|
+
bottom: 300
|
2805
|
+
});
|
2806
|
+
expect(rect(records[3].boundingClientRect)).to.eql(clientRect2);
|
2807
|
+
expect(rect(records[3].intersectionRect)).to.eql(intersectRect2);
|
2808
|
+
expect(records[3].isIntersecting).to.be(true);
|
2809
|
+
expect(records[3].intersectionRatio).to.be.within(0.53, 0.55); // ~0.54
|
2810
|
+
|
2811
|
+
done();
|
2812
|
+
io.disconnect();
|
2813
|
+
}, {}, parentRect);
|
2814
|
+
io.observe(documentElement);
|
2815
|
+
io.observe(body);
|
2816
|
+
io.observe(iframeTargetEl1);
|
2817
|
+
io.observe(iframeTargetEl2);
|
2818
|
+
});
|
2819
|
+
|
2820
|
+
it('calculates rects for a fully clipped frame', function(done) {
|
2821
|
+
var parentRect = rect({top: -400, left: 20, height: 300, width: 100});
|
2822
|
+
var io = createObserver(function(unsortedRecords) {
|
2823
|
+
var records = sortRecords(unsortedRecords);
|
2824
|
+
expect(records.length).to.be(3);
|
2825
|
+
checkRootBoundsAreNull(records);
|
2826
|
+
|
2827
|
+
var emptyRect = rect({
|
2828
|
+
top: 0,
|
2829
|
+
left: 0,
|
2830
|
+
width: 0,
|
2831
|
+
height: 0
|
2832
|
+
});
|
2833
|
+
|
2834
|
+
// The documentElement is completely invisible.
|
2835
|
+
expect(rect(records[0].boundingClientRect))
|
2836
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
2837
|
+
expect(rect(records[0].intersectionRect)).to.eql(emptyRect);
|
2838
|
+
expect(records[0].isIntersecting).to.be(false);
|
2839
|
+
expect(records[0].intersectionRatio).to.be(0);
|
2840
|
+
|
2841
|
+
// The document.body is completely invisible.
|
2842
|
+
expect(rect(records[1].boundingClientRect))
|
2843
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
2844
|
+
expect(rect(records[1].intersectionRect)).to.eql(emptyRect);
|
2845
|
+
expect(records[1].isIntersecting).to.be(false);
|
2846
|
+
expect(records[1].intersectionRatio).to.be(0);
|
2847
|
+
|
2848
|
+
// The target1 is completely invisible.
|
2849
|
+
var clientRect1 = rect({
|
2850
|
+
top: 0,
|
2851
|
+
left: 0,
|
2852
|
+
width: bodyWidth,
|
2853
|
+
height: 200
|
2854
|
+
});
|
2855
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
2856
|
+
expect(rect(records[2].intersectionRect)).to.eql(emptyRect);
|
2857
|
+
expect(records[2].isIntersecting).to.be(false);
|
2858
|
+
expect(records[2].intersectionRatio).to.be(0);
|
2859
|
+
|
2860
|
+
done();
|
2861
|
+
io.disconnect();
|
2862
|
+
}, {}, parentRect);
|
2863
|
+
io.observe(documentElement);
|
2864
|
+
io.observe(body);
|
2865
|
+
io.observe(iframeTargetEl1);
|
2866
|
+
});
|
2867
|
+
|
2868
|
+
it('blocks until crossOriginUpdater is called first time', function(done) {
|
2869
|
+
if (supportsNativeIntersectionObserver(iframeWin)) {
|
2870
|
+
// Skip: not possible to emulate with the native observer.
|
2871
|
+
done();
|
2872
|
+
return;
|
2873
|
+
}
|
2874
|
+
|
2875
|
+
var spy = sinon.spy();
|
2876
|
+
|
2877
|
+
var parentRect = rect({top: 0, left: 20, height: 300, width: 100});
|
2878
|
+
|
2879
|
+
var io = createObserver(spy, {});
|
2880
|
+
io.observe(iframeTargetEl1);
|
2881
|
+
|
2882
|
+
runSequence([
|
2883
|
+
function(done) {
|
2884
|
+
setTimeout(function() {
|
2885
|
+
expect(spy.callCount).to.be(0);
|
2886
|
+
|
2887
|
+
// Issue the first update.
|
2888
|
+
crossOriginUpdater(parentRect, null);
|
2889
|
+
|
2890
|
+
done();
|
2891
|
+
}, ASYNC_TIMEOUT);
|
2892
|
+
},
|
2893
|
+
function(done) {
|
2894
|
+
setTimeout(function() {
|
2895
|
+
expect(spy.callCount).to.be(1);
|
2896
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2897
|
+
expect(records.length).to.be(1);
|
2898
|
+
expect(records[0].intersectionRatio).to.be(0);
|
2899
|
+
expect(records[0].isIntersecting).to.be(false);
|
2900
|
+
done();
|
2901
|
+
}, ASYNC_TIMEOUT);
|
2902
|
+
},
|
2903
|
+
function(done) {
|
2904
|
+
io.disconnect();
|
2905
|
+
done();
|
2906
|
+
}
|
2907
|
+
], done);
|
2908
|
+
});
|
2909
|
+
|
2910
|
+
it('doesn\'t block with a root specified', function(done) {
|
2911
|
+
var spy = sinon.spy();
|
2912
|
+
|
2913
|
+
var io = createObserver(spy, {root: body});
|
2914
|
+
io.observe(iframeTargetEl1);
|
2915
|
+
|
2916
|
+
runSequence([
|
2917
|
+
function(done) {
|
2918
|
+
setTimeout(function() {
|
2919
|
+
expect(spy.callCount).to.be(1);
|
2920
|
+
var record = sortRecords(spy.lastCall.args[0])[0];
|
2921
|
+
expect(record.intersectionRatio).to.be(1);
|
2922
|
+
expect(record.isIntersecting).to.be(true);
|
2923
|
+
expect(rect(record.rootBounds)).to.eql(rect(body.getBoundingClientRect()));
|
2924
|
+
done();
|
2925
|
+
}, ASYNC_TIMEOUT);
|
2926
|
+
},
|
2927
|
+
function(done) {
|
2928
|
+
io.disconnect();
|
2929
|
+
done();
|
2930
|
+
}
|
2931
|
+
], done);
|
2932
|
+
});
|
2933
|
+
|
2934
|
+
it('handles style changes', function(done) {
|
2935
|
+
var spy = sinon.spy();
|
2936
|
+
|
2937
|
+
var parentRect = rect({top: 0, left: 0, height: 200, width: 100});
|
2938
|
+
|
2939
|
+
// When first element becomes invisible, the second element will show.
|
2940
|
+
// And in reverse: when the first element becomes visible again, the
|
2941
|
+
// second element will disappear.
|
2942
|
+
var io = createObserver(spy, {}, parentRect);
|
2943
|
+
io.observe(iframeTargetEl1);
|
2944
|
+
io.observe(iframeTargetEl2);
|
2945
|
+
|
2946
|
+
runSequence([
|
2947
|
+
function(done) {
|
2948
|
+
setTimeout(function() {
|
2949
|
+
expect(spy.callCount).to.be(1);
|
2950
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2951
|
+
expect(records.length).to.be(2);
|
2952
|
+
expect(records[0].intersectionRatio).to.be(1);
|
2953
|
+
expect(records[0].isIntersecting).to.be(true);
|
2954
|
+
expect(records[1].intersectionRatio).to.be(0);
|
2955
|
+
expect(records[1].isIntersecting).to.be(false);
|
2956
|
+
done();
|
2957
|
+
}, ASYNC_TIMEOUT);
|
2958
|
+
},
|
2959
|
+
function(done) {
|
2960
|
+
iframeTargetEl1.style.display = 'none';
|
2961
|
+
setTimeout(function() {
|
2962
|
+
expect(spy.callCount).to.be(2);
|
2963
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2964
|
+
expect(records.length).to.be(2);
|
2965
|
+
expect(records[0].intersectionRatio).to.be(0);
|
2966
|
+
expect(records[0].isIntersecting).to.be(false);
|
2967
|
+
expect(records[1].intersectionRatio).to.be(1);
|
2968
|
+
expect(records[1].isIntersecting).to.be(true);
|
2969
|
+
done();
|
2970
|
+
}, ASYNC_TIMEOUT);
|
2971
|
+
},
|
2972
|
+
function(done) {
|
2973
|
+
iframeTargetEl1.style.display = '';
|
2974
|
+
setTimeout(function() {
|
2975
|
+
expect(spy.callCount).to.be(3);
|
2976
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2977
|
+
expect(records.length).to.be(2);
|
2978
|
+
expect(records[0].intersectionRatio).to.be(1);
|
2979
|
+
expect(records[0].isIntersecting).to.be(true);
|
2980
|
+
expect(records[1].intersectionRatio).to.be(0);
|
2981
|
+
expect(records[1].isIntersecting).to.be(false);
|
2982
|
+
done();
|
2983
|
+
}, ASYNC_TIMEOUT);
|
2984
|
+
},
|
2985
|
+
function(done) {
|
2986
|
+
io.disconnect();
|
2987
|
+
done();
|
2988
|
+
}
|
2989
|
+
], done);
|
2990
|
+
});
|
2991
|
+
|
2992
|
+
it('handles scroll changes', function(done) {
|
2993
|
+
var spy = sinon.spy();
|
2994
|
+
|
2995
|
+
var parentRect = rect({top: 0, left: 0, height: 200, width: 100});
|
2996
|
+
|
2997
|
+
// Scrolling to the middle of the iframe shows the second box and
|
2998
|
+
// hides the first.
|
2999
|
+
var io = createObserver(spy, {}, parentRect);
|
3000
|
+
io.observe(iframeTargetEl1);
|
3001
|
+
io.observe(iframeTargetEl2);
|
3002
|
+
|
3003
|
+
runSequence([
|
3004
|
+
function(done) {
|
3005
|
+
setTimeout(function() {
|
3006
|
+
expect(spy.callCount).to.be(1);
|
3007
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
3008
|
+
expect(records.length).to.be(2);
|
3009
|
+
expect(records[0].intersectionRatio).to.be(1);
|
3010
|
+
expect(records[0].isIntersecting).to.be(true);
|
3011
|
+
expect(records[1].intersectionRatio).to.be(0);
|
3012
|
+
expect(records[1].isIntersecting).to.be(false);
|
3013
|
+
done();
|
3014
|
+
}, ASYNC_TIMEOUT);
|
3015
|
+
},
|
3016
|
+
function(done) {
|
3017
|
+
iframeWin.scrollTo(0, 202);
|
3018
|
+
setTimeout(function() {
|
3019
|
+
expect(spy.callCount).to.be(2);
|
3020
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
3021
|
+
expect(records.length).to.be(2);
|
3022
|
+
expect(records[0].intersectionRatio).to.be(0);
|
3023
|
+
expect(records[0].isIntersecting).to.be(false);
|
3024
|
+
expect(records[1].intersectionRatio).to.be(1);
|
3025
|
+
expect(records[1].isIntersecting).to.be(true);
|
3026
|
+
done();
|
3027
|
+
}, ASYNC_TIMEOUT);
|
3028
|
+
},
|
3029
|
+
function(done) {
|
3030
|
+
iframeWin.scrollTo(0, 0);
|
3031
|
+
setTimeout(function() {
|
3032
|
+
expect(spy.callCount).to.be(3);
|
3033
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
3034
|
+
expect(records.length).to.be(2);
|
3035
|
+
expect(records[0].intersectionRatio).to.be(1);
|
3036
|
+
expect(records[0].isIntersecting).to.be(true);
|
3037
|
+
expect(records[1].intersectionRatio).to.be(0);
|
3038
|
+
expect(records[1].isIntersecting).to.be(false);
|
3039
|
+
done();
|
3040
|
+
}, ASYNC_TIMEOUT);
|
3041
|
+
},
|
3042
|
+
function(done) {
|
3043
|
+
io.disconnect();
|
3044
|
+
done();
|
3045
|
+
}
|
3046
|
+
], done);
|
3047
|
+
});
|
3048
|
+
|
3049
|
+
it('handles parent rect changes', function(done) {
|
3050
|
+
var spy = sinon.spy();
|
3051
|
+
|
3052
|
+
var parentRect = rect({top: 0, left: 0, height: 200, width: 100});
|
3053
|
+
|
3054
|
+
// Iframe goes off screen and returns.
|
3055
|
+
var io = createObserver(spy, {}, parentRect);
|
3056
|
+
io.observe(iframeTargetEl1);
|
3057
|
+
io.observe(iframeTargetEl2);
|
3058
|
+
|
3059
|
+
runSequence([
|
3060
|
+
function(done) {
|
3061
|
+
setTimeout(function() {
|
3062
|
+
expect(spy.callCount).to.be(1);
|
3063
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
3064
|
+
expect(records.length).to.be(2);
|
3065
|
+
checkRootBoundsAreNull(records);
|
3066
|
+
expect(records[0].intersectionRatio).to.be(1);
|
3067
|
+
expect(records[0].isIntersecting).to.be(true);
|
3068
|
+
expect(records[1].intersectionRatio).to.be(0);
|
3069
|
+
expect(records[1].isIntersecting).to.be(false);
|
3070
|
+
// Top-level bounds.
|
3071
|
+
expect(records[0].intersectionRect.height).to.be(200);
|
3072
|
+
done();
|
3073
|
+
}, ASYNC_TIMEOUT);
|
3074
|
+
},
|
3075
|
+
function(done) {
|
3076
|
+
// Completely off screen.
|
3077
|
+
applyParentRect(rect({top: -202, left: 0, height: 200, width: 100}));
|
3078
|
+
setTimeout(function() {
|
3079
|
+
expect(spy.callCount).to.be(2);
|
3080
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
3081
|
+
expect(records.length).to.be(1);
|
3082
|
+
checkRootBoundsAreNull(records);
|
3083
|
+
expect(records[0].intersectionRatio).to.be(0);
|
3084
|
+
expect(records[0].isIntersecting).to.be(false);
|
3085
|
+
// Top-level bounds.
|
3086
|
+
expect(records[0].intersectionRect.height).to.be(0);
|
3087
|
+
done();
|
3088
|
+
}, ASYNC_TIMEOUT);
|
3089
|
+
},
|
3090
|
+
function(done) {
|
3091
|
+
// Partially returns.
|
3092
|
+
applyParentRect(rect({top: -100, left: 0, height: 200, width: 100}));
|
3093
|
+
setTimeout(function() {
|
3094
|
+
expect(spy.callCount).to.be(3);
|
3095
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
3096
|
+
expect(records.length).to.be(1);
|
3097
|
+
checkRootBoundsAreNull(records);
|
3098
|
+
expect(records[0].intersectionRatio).to.be.within(0.45, 0.55);
|
3099
|
+
expect(records[0].isIntersecting).to.be(true);
|
3100
|
+
// Top-level bounds.
|
3101
|
+
expect(records[0].intersectionRect.height / 200).to.be.within(0.45, 0.55);
|
3102
|
+
done();
|
3103
|
+
}, ASYNC_TIMEOUT);
|
3104
|
+
},
|
3105
|
+
function(done) {
|
3106
|
+
io.disconnect();
|
3107
|
+
done();
|
3108
|
+
}
|
3109
|
+
], done);
|
3110
|
+
});
|
3111
|
+
|
3112
|
+
it('handles tracking iframe viewport', function(done) {
|
3113
|
+
iframe.style.height = '100px';
|
3114
|
+
iframe.style.top = '100px';
|
3115
|
+
iframeWin.scrollTo(0, 110);
|
3116
|
+
// {root:iframeDoc} means to track the iframe viewport.
|
3117
|
+
var io = createObserver(
|
3118
|
+
function (records) {
|
3119
|
+
io.unobserve(iframeTargetEl1);
|
3120
|
+
var intersectionRect = rect({
|
3121
|
+
top: 0, // if root=null, then this would be 100.
|
3122
|
+
left: 0,
|
3123
|
+
height: 90,
|
3124
|
+
width: bodyWidth
|
3125
|
+
});
|
3126
|
+
expect(records.length).to.be(1);
|
3127
|
+
expect(rect(records[0].rootBounds)).to.eql(getRootRect(iframeDoc));
|
3128
|
+
expect(rect(records[0].intersectionRect)).to.eql(intersectionRect);
|
3129
|
+
done();
|
3130
|
+
},
|
3131
|
+
{ root: iframeDoc }
|
3132
|
+
);
|
3133
|
+
|
3134
|
+
io.observe(iframeTargetEl1);
|
3135
|
+
});
|
3136
|
+
|
3137
|
+
it('handles tracking iframe viewport with rootMargin', function(done) {
|
3138
|
+
iframe.style.height = '100px';
|
3139
|
+
|
3140
|
+
var io = createObserver(
|
3141
|
+
function (records) {
|
3142
|
+
io.unobserve(iframeTargetEl1);
|
3143
|
+
var intersectionRect = rect({
|
3144
|
+
top: 0, // if root=null, then this would be 100.
|
3145
|
+
left: 0,
|
3146
|
+
height: 200,
|
3147
|
+
width: bodyWidth
|
3148
|
+
});
|
3149
|
+
|
3150
|
+
// rootMargin: 100% --> 3x width + 3x height.
|
3151
|
+
var expectedRootBounds = rect({
|
3152
|
+
top: -100,
|
3153
|
+
left: -bodyWidth,
|
3154
|
+
width: bodyWidth * 3,
|
3155
|
+
height: 100 * 3
|
3156
|
+
});
|
3157
|
+
expect(records.length).to.be(1);
|
3158
|
+
expect(rect(records[0].rootBounds)).to.eql(expectedRootBounds);
|
3159
|
+
expect(rect(records[0].intersectionRect)).to.eql(intersectionRect);
|
3160
|
+
done();
|
3161
|
+
},
|
3162
|
+
{ root: iframeDoc, rootMargin: '100%' }
|
3163
|
+
);
|
3164
|
+
|
3165
|
+
io.observe(iframeTargetEl1);
|
3166
|
+
});
|
3167
|
+
});
|
2365
3168
|
});
|
2366
3169
|
});
|
2367
3170
|
|
package/intersection-observer.js
CHANGED
@@ -33,12 +33,32 @@ if ('IntersectionObserver' in window &&
|
|
33
33
|
return;
|
34
34
|
}
|
35
35
|
|
36
|
-
|
37
36
|
/**
|
38
|
-
*
|
37
|
+
* Returns the embedding frame element, if any.
|
38
|
+
* @param {!Document} doc
|
39
|
+
* @return {!Element}
|
39
40
|
*/
|
40
|
-
|
41
|
+
function getFrameElement(doc) {
|
42
|
+
try {
|
43
|
+
return doc.defaultView && doc.defaultView.frameElement || null;
|
44
|
+
} catch (e) {
|
45
|
+
// Ignore the error.
|
46
|
+
return null;
|
47
|
+
}
|
48
|
+
}
|
41
49
|
|
50
|
+
/**
|
51
|
+
* A local reference to the root document.
|
52
|
+
*/
|
53
|
+
var 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);
|
42
62
|
|
43
63
|
/**
|
44
64
|
* An IntersectionObserver registry. This registry exists to hold a strong
|
@@ -111,8 +131,12 @@ function IntersectionObserver(callback, opt_options) {
|
|
111
131
|
throw new Error('callback must be a function');
|
112
132
|
}
|
113
133
|
|
114
|
-
if (
|
115
|
-
|
134
|
+
if (
|
135
|
+
options.root &&
|
136
|
+
options.root.nodeType != 1 &&
|
137
|
+
options.root.nodeType != 9
|
138
|
+
) {
|
139
|
+
throw new Error('root must be a Document or Element');
|
116
140
|
}
|
117
141
|
|
118
142
|
// Binds and throttles `this._checkForIntersections`.
|
@@ -375,7 +399,9 @@ IntersectionObserver.prototype._monitorIntersections = function(doc) {
|
|
375
399
|
});
|
376
400
|
|
377
401
|
// Also monitor the parent.
|
378
|
-
|
402
|
+
var rootDoc =
|
403
|
+
(this.root && (this.root.ownerDocument || this.root)) || document;
|
404
|
+
if (doc != rootDoc) {
|
379
405
|
var frame = getFrameElement(doc);
|
380
406
|
if (frame) {
|
381
407
|
this._monitorIntersections(frame.ownerDocument);
|
@@ -395,7 +421,8 @@ IntersectionObserver.prototype._unmonitorIntersections = function(doc) {
|
|
395
421
|
return;
|
396
422
|
}
|
397
423
|
|
398
|
-
var rootDoc =
|
424
|
+
var rootDoc =
|
425
|
+
(this.root && (this.root.ownerDocument || this.root)) || document;
|
399
426
|
|
400
427
|
// Check if any dependent targets are still remaining.
|
401
428
|
var hasDependentTargets =
|
@@ -473,11 +500,18 @@ IntersectionObserver.prototype._checkForIntersections = function() {
|
|
473
500
|
var intersectionRect = rootIsInDom && rootContainsTarget &&
|
474
501
|
this._computeTargetAndRootIntersection(target, targetRect, rootRect);
|
475
502
|
|
503
|
+
var rootBounds = null;
|
504
|
+
if (!this._rootContainsTarget(target)) {
|
505
|
+
rootBounds = getEmptyRect();
|
506
|
+
} else if (!crossOriginUpdater || this.root) {
|
507
|
+
rootBounds = rootRect;
|
508
|
+
}
|
509
|
+
|
476
510
|
var newEntry = item.entry = new IntersectionObserverEntry({
|
477
511
|
time: now(),
|
478
512
|
target: target,
|
479
513
|
boundingClientRect: targetRect,
|
480
|
-
rootBounds:
|
514
|
+
rootBounds: rootBounds,
|
481
515
|
intersectionRect: intersectionRect
|
482
516
|
});
|
483
517
|
|
@@ -598,12 +632,13 @@ IntersectionObserver.prototype._computeTargetAndRootIntersection =
|
|
598
632
|
*/
|
599
633
|
IntersectionObserver.prototype._getRootRect = function() {
|
600
634
|
var rootRect;
|
601
|
-
if (this.root) {
|
635
|
+
if (this.root && !isDoc(this.root)) {
|
602
636
|
rootRect = getBoundingClientRect(this.root);
|
603
637
|
} else {
|
604
638
|
// Use <html>/<body> instead of window since scroll bars affect size.
|
605
|
-
var
|
606
|
-
var
|
639
|
+
var doc = isDoc(this.root) ? this.root : document;
|
640
|
+
var html = doc.documentElement;
|
641
|
+
var body = doc.body;
|
607
642
|
rootRect = {
|
608
643
|
top: 0,
|
609
644
|
left: 0,
|
@@ -694,8 +729,12 @@ IntersectionObserver.prototype._rootIsInDom = function() {
|
|
694
729
|
* @private
|
695
730
|
*/
|
696
731
|
IntersectionObserver.prototype._rootContainsTarget = function(target) {
|
697
|
-
|
698
|
-
(
|
732
|
+
var rootDoc =
|
733
|
+
(this.root && (this.root.ownerDocument || this.root)) || document;
|
734
|
+
return (
|
735
|
+
containsDeep(rootDoc, target) &&
|
736
|
+
(!this.root || rootDoc == target.ownerDocument)
|
737
|
+
);
|
699
738
|
};
|
700
739
|
|
701
740
|
|
@@ -782,8 +821,8 @@ function removeEvent(node, event, fn, opt_useCapture) {
|
|
782
821
|
if (typeof node.removeEventListener == 'function') {
|
783
822
|
node.removeEventListener(event, fn, opt_useCapture || false);
|
784
823
|
}
|
785
|
-
else if (typeof node.
|
786
|
-
node.
|
824
|
+
else if (typeof node.detachEvent == 'function') {
|
825
|
+
node.detachEvent('on' + event, fn);
|
787
826
|
}
|
788
827
|
}
|
789
828
|
|
@@ -945,32 +984,26 @@ function getParentNode(node) {
|
|
945
984
|
return getFrameElement(node);
|
946
985
|
}
|
947
986
|
|
987
|
+
// If the parent has element that is assigned through shadow root slot
|
988
|
+
if (parent && parent.assignedSlot) {
|
989
|
+
parent = parent.assignedSlot.parentNode
|
990
|
+
}
|
991
|
+
|
948
992
|
if (parent && parent.nodeType == 11 && parent.host) {
|
949
993
|
// If the parent is a shadow root, return the host element.
|
950
994
|
return parent.host;
|
951
995
|
}
|
952
996
|
|
953
|
-
if (parent && parent.assignedSlot) {
|
954
|
-
// If the parent is distributed in a <slot>, return the parent of a slot.
|
955
|
-
return parent.assignedSlot.parentNode;
|
956
|
-
}
|
957
|
-
|
958
997
|
return parent;
|
959
998
|
}
|
960
999
|
|
961
|
-
|
962
1000
|
/**
|
963
|
-
* Returns
|
964
|
-
* @param {!
|
965
|
-
* @
|
1001
|
+
* Returns true if `node` is a Document.
|
1002
|
+
* @param {!Node} node
|
1003
|
+
* @returns {boolean}
|
966
1004
|
*/
|
967
|
-
function
|
968
|
-
|
969
|
-
return doc.defaultView && doc.defaultView.frameElement || null;
|
970
|
-
} catch (e) {
|
971
|
-
// Ignore the error.
|
972
|
-
return null;
|
973
|
-
}
|
1005
|
+
function isDoc(node) {
|
1006
|
+
return node && node.nodeType === 9;
|
974
1007
|
}
|
975
1008
|
|
976
1009
|
|
package/package.json
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
{
|
2
2
|
"name": "intersection-observer",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.12.1",
|
4
4
|
"description": "A polyfill for IntersectionObserver",
|
5
|
-
"main": "intersection-observer",
|
5
|
+
"main": "intersection-observer.js",
|
6
6
|
"repository": {
|
7
7
|
"type": "git",
|
8
8
|
"url": "git@github.com:w3c/IntersectionObserver.git"
|