intersection-observer 0.10.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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"
|