intersection-observer 0.5.1 → 0.12.2
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 +76 -8
- package/intersection-observer-test.html +7 -0
- package/intersection-observer-test.js +2255 -4
- package/intersection-observer.js +363 -75
- package/package.json +8 -8
- package/border-test.html +0 -47
- package/frame1.html +0 -9
- package/frame2.html +0 -9
- package/frames.html +0 -33
- package/slot.html +0 -93
@@ -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();
|
@@ -157,6 +160,21 @@ describe('IntersectionObserver', function() {
|
|
157
160
|
}).to.throwException();
|
158
161
|
});
|
159
162
|
|
163
|
+
it('fills in x and y in the resulting rects', function(done) {
|
164
|
+
io = new IntersectionObserver(function(records) {
|
165
|
+
expect(records.length).to.be(1);
|
166
|
+
var entry = records[0];
|
167
|
+
expect(entry.rootBounds.x).to.be(entry.rootBounds.left);
|
168
|
+
expect(entry.rootBounds.y).to.be(entry.rootBounds.top);
|
169
|
+
expect(entry.boundingClientRect.x).to.be(entry.boundingClientRect.left);
|
170
|
+
expect(entry.boundingClientRect.y).to.be(entry.boundingClientRect.top);
|
171
|
+
expect(entry.intersectionRect.x).to.be(entry.intersectionRect.left);
|
172
|
+
expect(entry.intersectionRect.y).to.be(entry.intersectionRect.top);
|
173
|
+
done();
|
174
|
+
}, {root: rootEl});
|
175
|
+
targetEl2.style.top = '-40px';
|
176
|
+
io.observe(targetEl1);
|
177
|
+
});
|
160
178
|
|
161
179
|
it('triggers for all targets when observing begins', function(done) {
|
162
180
|
io = new IntersectionObserver(function(records) {
|
@@ -724,6 +742,30 @@ describe('IntersectionObserver', function() {
|
|
724
742
|
|
725
743
|
io.observe(targetEl1);
|
726
744
|
});
|
745
|
+
|
746
|
+
it('handles roots in shadow DOM', function(done) {
|
747
|
+
var shadowRoot = grandParentEl.attachShadow({mode: 'open'});
|
748
|
+
|
749
|
+
shadowRoot.innerHTML =
|
750
|
+
'<style>' +
|
751
|
+
'#slot-parent {' +
|
752
|
+
' position: relative;' +
|
753
|
+
' width: 400px;' +
|
754
|
+
' height: 200px;' +
|
755
|
+
'}' +
|
756
|
+
'</style>' +
|
757
|
+
'<div id="slot-parent"><slot></slot></div>';
|
758
|
+
|
759
|
+
var slotParent = shadowRoot.getElementById('slot-parent');
|
760
|
+
|
761
|
+
io = new IntersectionObserver(function(records) {
|
762
|
+
expect(records.length).to.be(1);
|
763
|
+
expect(records[0].intersectionRatio).to.be(1);
|
764
|
+
done();
|
765
|
+
}, {root: slotParent});
|
766
|
+
|
767
|
+
io.observe(targetEl1);
|
768
|
+
});
|
727
769
|
}
|
728
770
|
|
729
771
|
|
@@ -917,6 +959,2213 @@ describe('IntersectionObserver', function() {
|
|
917
959
|
|
918
960
|
});
|
919
961
|
|
962
|
+
describe('iframe', function() {
|
963
|
+
var iframe;
|
964
|
+
var iframeWin, iframeDoc;
|
965
|
+
var documentElement, body;
|
966
|
+
var iframeTargetEl1, iframeTargetEl2;
|
967
|
+
var bodyWidth;
|
968
|
+
|
969
|
+
beforeEach(function(done) {
|
970
|
+
iframe = document.createElement('iframe');
|
971
|
+
iframe.setAttribute('frameborder', '0');
|
972
|
+
iframe.setAttribute('scrolling', 'yes');
|
973
|
+
iframe.style.position = 'fixed';
|
974
|
+
iframe.style.top = '0px';
|
975
|
+
iframe.style.width = '100px';
|
976
|
+
iframe.style.height = '200px';
|
977
|
+
iframe.onerror = function() {
|
978
|
+
done(new Error('iframe initialization failed'));
|
979
|
+
};
|
980
|
+
iframe.onload = function() {
|
981
|
+
iframe.onload = null;
|
982
|
+
iframeWin = iframe.contentWindow;
|
983
|
+
iframeDoc = iframeWin.document;
|
984
|
+
iframeDoc.open();
|
985
|
+
iframeDoc.write('<!DOCTYPE html><html><body>');
|
986
|
+
iframeDoc.write('<style>');
|
987
|
+
iframeDoc.write('body {margin: 0}');
|
988
|
+
iframeDoc.write('.target {height: 200px; margin-bottom: 2px; background: blue;}');
|
989
|
+
iframeDoc.write('</style>');
|
990
|
+
iframeDoc.close();
|
991
|
+
|
992
|
+
// Ensure the documentElement and body are always sorted on top. See
|
993
|
+
// `sortRecords` for more info.
|
994
|
+
documentElement = iframeDoc.documentElement;
|
995
|
+
body = iframeDoc.body;
|
996
|
+
documentElement.id = 'A1';
|
997
|
+
body.id = 'A1';
|
998
|
+
|
999
|
+
function createTarget(id, bg) {
|
1000
|
+
var target = iframeDoc.createElement('div');
|
1001
|
+
target.id = id;
|
1002
|
+
target.className = 'target';
|
1003
|
+
target.style.background = bg;
|
1004
|
+
iframeDoc.body.appendChild(target);
|
1005
|
+
return target;
|
1006
|
+
}
|
1007
|
+
iframeTargetEl1 = createTarget('target1', 'blue');
|
1008
|
+
iframeTargetEl2 = createTarget('target2', 'green');
|
1009
|
+
bodyWidth = iframeDoc.body.clientWidth;
|
1010
|
+
done();
|
1011
|
+
};
|
1012
|
+
iframe.src = 'about:blank';
|
1013
|
+
rootEl.appendChild(iframe);
|
1014
|
+
});
|
1015
|
+
|
1016
|
+
afterEach(function() {
|
1017
|
+
rootEl.removeChild(iframe);
|
1018
|
+
});
|
1019
|
+
|
1020
|
+
function rect(r) {
|
1021
|
+
return {
|
1022
|
+
y: typeof r.y == 'number' ? r.y : r.top,
|
1023
|
+
x: typeof r.x == 'number' ? r.x : r.left,
|
1024
|
+
top: r.top,
|
1025
|
+
left: r.left,
|
1026
|
+
width: r.width != null ? r.width : r.right - r.left,
|
1027
|
+
height: r.height != null ? r.height : r.bottom - r.top,
|
1028
|
+
right: r.right != null ? r.right : r.left + r.width,
|
1029
|
+
bottom: r.bottom != null ? r.bottom : r.top + r.height
|
1030
|
+
};
|
1031
|
+
}
|
1032
|
+
|
1033
|
+
function getRootRect(doc) {
|
1034
|
+
var html = doc.documentElement;
|
1035
|
+
var body = doc.body;
|
1036
|
+
return rect({
|
1037
|
+
top: 0,
|
1038
|
+
left: 0,
|
1039
|
+
right: html.clientWidth || body.clientWidth,
|
1040
|
+
width: html.clientWidth || body.clientWidth,
|
1041
|
+
bottom: html.clientHeight || body.clientHeight,
|
1042
|
+
height: html.clientHeight || body.clientHeight
|
1043
|
+
});
|
1044
|
+
}
|
1045
|
+
|
1046
|
+
describe('same-origin iframe loaded in the mainframe', function() {
|
1047
|
+
it('iframe targets do not intersect with a top root element', function(done) {
|
1048
|
+
var io = new IntersectionObserver(function(unsortedRecords) {
|
1049
|
+
var records = sortRecords(unsortedRecords);
|
1050
|
+
expect(records.length).to.be(2);
|
1051
|
+
expect(records[0].isIntersecting).to.be(false);
|
1052
|
+
expect(records[1].isIntersecting).to.be(false);
|
1053
|
+
done();
|
1054
|
+
io.disconnect();
|
1055
|
+
}, {root: rootEl});
|
1056
|
+
io.observe(iframeTargetEl1);
|
1057
|
+
io.observe(iframeTargetEl2);
|
1058
|
+
});
|
1059
|
+
|
1060
|
+
it('triggers for all targets in top-level root', function(done) {
|
1061
|
+
var io = new IntersectionObserver(function(unsortedRecords) {
|
1062
|
+
var records = sortRecords(unsortedRecords);
|
1063
|
+
expect(records.length).to.be(2);
|
1064
|
+
expect(records[0].isIntersecting).to.be(true);
|
1065
|
+
expect(records[0].intersectionRatio).to.be(1);
|
1066
|
+
expect(records[1].isIntersecting).to.be(false);
|
1067
|
+
expect(records[1].intersectionRatio).to.be(0);
|
1068
|
+
|
1069
|
+
// The rootBounds is for the document's root.
|
1070
|
+
expect(records[0].rootBounds.height).to.be(innerHeight);
|
1071
|
+
|
1072
|
+
done();
|
1073
|
+
io.disconnect();
|
1074
|
+
});
|
1075
|
+
io.observe(iframeTargetEl1);
|
1076
|
+
io.observe(iframeTargetEl2);
|
1077
|
+
});
|
1078
|
+
|
1079
|
+
it('triggers for all targets in iframe-level root', function(done) {
|
1080
|
+
var io = new IntersectionObserver(function(unsortedRecords) {
|
1081
|
+
var records = sortRecords(unsortedRecords);
|
1082
|
+
expect(records.length).to.be(2);
|
1083
|
+
expect(records[0].intersectionRatio).to.be(1);
|
1084
|
+
expect(records[1].intersectionRatio).to.be(1);
|
1085
|
+
|
1086
|
+
// The rootBounds is for the document's root.
|
1087
|
+
expect(rect(records[0].rootBounds)).
|
1088
|
+
to.eql(rect(iframeDoc.body.getBoundingClientRect()));
|
1089
|
+
|
1090
|
+
done();
|
1091
|
+
io.disconnect();
|
1092
|
+
}, {root: iframeDoc.body});
|
1093
|
+
io.observe(iframeTargetEl1);
|
1094
|
+
io.observe(iframeTargetEl2);
|
1095
|
+
});
|
1096
|
+
|
1097
|
+
it('calculates rects for a fully visible frame', function(done) {
|
1098
|
+
iframe.style.top = '0px';
|
1099
|
+
iframe.style.height = '300px';
|
1100
|
+
var io = new IntersectionObserver(function(unsortedRecords) {
|
1101
|
+
var records = sortRecords(unsortedRecords);
|
1102
|
+
expect(records.length).to.be(2);
|
1103
|
+
expect(rect(records[0].rootBounds)).to.eql(getRootRect(document));
|
1104
|
+
expect(rect(records[1].rootBounds)).to.eql(getRootRect(document));
|
1105
|
+
|
1106
|
+
// The target1 is fully visible.
|
1107
|
+
var clientRect1 = rect({
|
1108
|
+
top: 0,
|
1109
|
+
left: 0,
|
1110
|
+
width: bodyWidth,
|
1111
|
+
height: 200
|
1112
|
+
});
|
1113
|
+
expect(rect(records[0].boundingClientRect)).to.eql(clientRect1);
|
1114
|
+
expect(rect(records[0].intersectionRect)).to.eql(clientRect1);
|
1115
|
+
expect(records[0].isIntersecting).to.be(true);
|
1116
|
+
expect(records[0].intersectionRatio).to.be(1);
|
1117
|
+
|
1118
|
+
// The target2 is partially visible.
|
1119
|
+
var clientRect2 = rect({
|
1120
|
+
top: 202,
|
1121
|
+
left: 0,
|
1122
|
+
width: bodyWidth,
|
1123
|
+
height: 200
|
1124
|
+
});
|
1125
|
+
var intersectRect2 = rect({
|
1126
|
+
top: 202,
|
1127
|
+
left: 0,
|
1128
|
+
width: bodyWidth,
|
1129
|
+
// The bottom is clipped off.
|
1130
|
+
bottom: 300
|
1131
|
+
});
|
1132
|
+
expect(rect(records[1].boundingClientRect)).to.eql(clientRect2);
|
1133
|
+
expect(rect(records[1].intersectionRect)).to.eql(intersectRect2);
|
1134
|
+
expect(records[1].isIntersecting).to.be(true);
|
1135
|
+
expect(records[1].intersectionRatio).to.be.within(0.48, 0.5); // ~0.5
|
1136
|
+
|
1137
|
+
done();
|
1138
|
+
io.disconnect();
|
1139
|
+
});
|
1140
|
+
io.observe(iframeTargetEl1);
|
1141
|
+
io.observe(iframeTargetEl2);
|
1142
|
+
});
|
1143
|
+
|
1144
|
+
it('calculates rects for a fully visible and offset frame', function(done) {
|
1145
|
+
iframe.style.top = '10px';
|
1146
|
+
iframe.style.height = '300px';
|
1147
|
+
var io = new IntersectionObserver(function(unsortedRecords) {
|
1148
|
+
var records = sortRecords(unsortedRecords);
|
1149
|
+
expect(records.length).to.be(2);
|
1150
|
+
expect(rect(records[0].rootBounds)).to.eql(getRootRect(document));
|
1151
|
+
expect(rect(records[1].rootBounds)).to.eql(getRootRect(document));
|
1152
|
+
|
1153
|
+
// The target1 is fully visible.
|
1154
|
+
var clientRect1 = rect({
|
1155
|
+
top: 0,
|
1156
|
+
left: 0,
|
1157
|
+
width: bodyWidth,
|
1158
|
+
height: 200
|
1159
|
+
});
|
1160
|
+
expect(rect(records[0].boundingClientRect)).to.eql(clientRect1);
|
1161
|
+
expect(rect(records[0].intersectionRect)).to.eql(clientRect1);
|
1162
|
+
expect(records[0].isIntersecting).to.be(true);
|
1163
|
+
expect(records[0].intersectionRatio).to.be(1);
|
1164
|
+
|
1165
|
+
// The target2 is partially visible.
|
1166
|
+
var clientRect2 = rect({
|
1167
|
+
top: 202,
|
1168
|
+
left: 0,
|
1169
|
+
width: bodyWidth,
|
1170
|
+
height: 200
|
1171
|
+
});
|
1172
|
+
var intersectRect2 = rect({
|
1173
|
+
top: 202,
|
1174
|
+
left: 0,
|
1175
|
+
width: bodyWidth,
|
1176
|
+
// The bottom is clipped off.
|
1177
|
+
bottom: 300
|
1178
|
+
});
|
1179
|
+
expect(rect(records[1].boundingClientRect)).to.eql(clientRect2);
|
1180
|
+
expect(rect(records[1].intersectionRect)).to.eql(intersectRect2);
|
1181
|
+
expect(records[1].isIntersecting).to.be(true);
|
1182
|
+
expect(records[1].intersectionRatio).to.be.within(0.48, 0.5); // ~0.5
|
1183
|
+
|
1184
|
+
done();
|
1185
|
+
io.disconnect();
|
1186
|
+
});
|
1187
|
+
io.observe(iframeTargetEl1);
|
1188
|
+
io.observe(iframeTargetEl2);
|
1189
|
+
});
|
1190
|
+
|
1191
|
+
it('calculates rects for a clipped frame on top', function(done) {
|
1192
|
+
iframe.style.top = '-10px';
|
1193
|
+
iframe.style.height = '300px';
|
1194
|
+
var io = new IntersectionObserver(function(unsortedRecords) {
|
1195
|
+
var records = sortRecords(unsortedRecords);
|
1196
|
+
expect(records.length).to.be(2);
|
1197
|
+
expect(rect(records[0].rootBounds)).to.eql(getRootRect(document));
|
1198
|
+
expect(rect(records[1].rootBounds)).to.eql(getRootRect(document));
|
1199
|
+
|
1200
|
+
// The target1 is clipped at the top by the iframe's clipping.
|
1201
|
+
var clientRect1 = rect({
|
1202
|
+
top: 0,
|
1203
|
+
left: 0,
|
1204
|
+
width: bodyWidth,
|
1205
|
+
height: 200
|
1206
|
+
});
|
1207
|
+
var intersectRect1 = rect({
|
1208
|
+
left: 0,
|
1209
|
+
width: bodyWidth,
|
1210
|
+
// Top is clipped.
|
1211
|
+
top: 10,
|
1212
|
+
height: 200 - 10
|
1213
|
+
});
|
1214
|
+
expect(rect(records[0].boundingClientRect)).to.eql(clientRect1);
|
1215
|
+
expect(rect(records[0].intersectionRect)).to.eql(intersectRect1);
|
1216
|
+
expect(records[0].isIntersecting).to.be(true);
|
1217
|
+
expect(records[0].intersectionRatio).to.within(0.94, 0.96); // ~0.95
|
1218
|
+
|
1219
|
+
// The target2 is partially visible.
|
1220
|
+
var clientRect2 = rect({
|
1221
|
+
top: 202,
|
1222
|
+
left: 0,
|
1223
|
+
width: bodyWidth,
|
1224
|
+
height: 200
|
1225
|
+
});
|
1226
|
+
var intersectRect2 = rect({
|
1227
|
+
top: 202,
|
1228
|
+
left: 0,
|
1229
|
+
width: bodyWidth,
|
1230
|
+
// The bottom is clipped off.
|
1231
|
+
bottom: 300
|
1232
|
+
});
|
1233
|
+
expect(rect(records[1].boundingClientRect)).to.eql(clientRect2);
|
1234
|
+
expect(rect(records[1].intersectionRect)).to.eql(intersectRect2);
|
1235
|
+
expect(records[1].isIntersecting).to.be(true);
|
1236
|
+
expect(records[1].intersectionRatio).to.be.within(0.48, 0.5); // ~0.49
|
1237
|
+
|
1238
|
+
done();
|
1239
|
+
io.disconnect();
|
1240
|
+
});
|
1241
|
+
io.observe(iframeTargetEl1);
|
1242
|
+
io.observe(iframeTargetEl2);
|
1243
|
+
});
|
1244
|
+
|
1245
|
+
it('calculates rects for a clipped frame on bottom', function(done) {
|
1246
|
+
iframe.style.top = 'auto';
|
1247
|
+
iframe.style.bottom = '-10px';
|
1248
|
+
iframe.style.height = '300px';
|
1249
|
+
var io = new IntersectionObserver(function(unsortedRecords) {
|
1250
|
+
var records = sortRecords(unsortedRecords);
|
1251
|
+
expect(records.length).to.be(2);
|
1252
|
+
expect(rect(records[0].rootBounds)).to.eql(getRootRect(document));
|
1253
|
+
expect(rect(records[1].rootBounds)).to.eql(getRootRect(document));
|
1254
|
+
|
1255
|
+
// The target1 is clipped at the top by the iframe's clipping.
|
1256
|
+
var clientRect1 = rect({
|
1257
|
+
top: 0,
|
1258
|
+
left: 0,
|
1259
|
+
width: bodyWidth,
|
1260
|
+
height: 200
|
1261
|
+
});
|
1262
|
+
expect(rect(records[0].boundingClientRect)).to.eql(clientRect1);
|
1263
|
+
expect(rect(records[0].intersectionRect)).to.eql(clientRect1);
|
1264
|
+
expect(records[0].isIntersecting).to.be(true);
|
1265
|
+
expect(records[0].intersectionRatio).to.be(1);
|
1266
|
+
|
1267
|
+
// The target2 is partially visible.
|
1268
|
+
var clientRect2 = rect({
|
1269
|
+
top: 202,
|
1270
|
+
left: 0,
|
1271
|
+
width: bodyWidth,
|
1272
|
+
height: 200
|
1273
|
+
});
|
1274
|
+
var intersectRect2 = rect({
|
1275
|
+
top: 202,
|
1276
|
+
left: 0,
|
1277
|
+
width: bodyWidth,
|
1278
|
+
// The bottom is clipped off.
|
1279
|
+
bottom: 300 - 10
|
1280
|
+
});
|
1281
|
+
expect(rect(records[1].boundingClientRect)).to.eql(clientRect2);
|
1282
|
+
expect(rect(records[1].intersectionRect)).to.eql(intersectRect2);
|
1283
|
+
expect(records[1].isIntersecting).to.be(true);
|
1284
|
+
expect(records[1].intersectionRatio).to.be.within(0.43, 0.45); // ~0.44
|
1285
|
+
|
1286
|
+
done();
|
1287
|
+
io.disconnect();
|
1288
|
+
});
|
1289
|
+
io.observe(iframeTargetEl1);
|
1290
|
+
io.observe(iframeTargetEl2);
|
1291
|
+
});
|
1292
|
+
|
1293
|
+
it('calculates rects for a fully visible frame and scrolled', function(done) {
|
1294
|
+
iframe.style.top = '0px';
|
1295
|
+
iframe.style.height = '300px';
|
1296
|
+
iframeWin.scrollTo(0, 10);
|
1297
|
+
var io = new IntersectionObserver(function(unsortedRecords) {
|
1298
|
+
var records = sortRecords(unsortedRecords);
|
1299
|
+
expect(records.length).to.be(2);
|
1300
|
+
expect(rect(records[0].rootBounds)).to.eql(getRootRect(document));
|
1301
|
+
expect(rect(records[1].rootBounds)).to.eql(getRootRect(document));
|
1302
|
+
|
1303
|
+
// The target1 is fully visible.
|
1304
|
+
var clientRect1 = rect({
|
1305
|
+
top: -10,
|
1306
|
+
left: 0,
|
1307
|
+
width: bodyWidth,
|
1308
|
+
height: 200
|
1309
|
+
});
|
1310
|
+
var intersectRect1 = rect({
|
1311
|
+
top: 0,
|
1312
|
+
left: 0,
|
1313
|
+
width: bodyWidth,
|
1314
|
+
// Height is only for the visible area.
|
1315
|
+
height: 200 - 10
|
1316
|
+
});
|
1317
|
+
expect(rect(records[0].boundingClientRect)).to.eql(clientRect1);
|
1318
|
+
expect(rect(records[0].intersectionRect)).to.eql(intersectRect1);
|
1319
|
+
expect(records[0].isIntersecting).to.be(true);
|
1320
|
+
expect(records[0].intersectionRatio).to.within(0.94, 0.96); // ~0.95
|
1321
|
+
|
1322
|
+
// The target2 is partially visible.
|
1323
|
+
var clientRect2 = rect({
|
1324
|
+
top: 202 - 10,
|
1325
|
+
left: 0,
|
1326
|
+
width: bodyWidth,
|
1327
|
+
height: 200
|
1328
|
+
});
|
1329
|
+
var intersectRect2 = rect({
|
1330
|
+
top: 202 - 10,
|
1331
|
+
left: 0,
|
1332
|
+
width: bodyWidth,
|
1333
|
+
// The bottom is clipped off.
|
1334
|
+
bottom: 300
|
1335
|
+
});
|
1336
|
+
expect(rect(records[1].boundingClientRect)).to.eql(clientRect2);
|
1337
|
+
expect(rect(records[1].intersectionRect)).to.eql(intersectRect2);
|
1338
|
+
expect(records[1].isIntersecting).to.be(true);
|
1339
|
+
expect(records[1].intersectionRatio).to.be.within(0.53, 0.55); // ~0.54
|
1340
|
+
|
1341
|
+
done();
|
1342
|
+
io.disconnect();
|
1343
|
+
});
|
1344
|
+
io.observe(iframeTargetEl1);
|
1345
|
+
io.observe(iframeTargetEl2);
|
1346
|
+
});
|
1347
|
+
|
1348
|
+
it('calculates rects for a clipped frame on top and scrolled', function(done) {
|
1349
|
+
iframe.style.top = '-10px';
|
1350
|
+
iframe.style.height = '300px';
|
1351
|
+
iframeWin.scrollTo(0, 10);
|
1352
|
+
var io = new IntersectionObserver(function(unsortedRecords) {
|
1353
|
+
var records = sortRecords(unsortedRecords);
|
1354
|
+
expect(records.length).to.be(2);
|
1355
|
+
expect(rect(records[0].rootBounds)).to.eql(getRootRect(document));
|
1356
|
+
expect(rect(records[1].rootBounds)).to.eql(getRootRect(document));
|
1357
|
+
|
1358
|
+
// The target1 is clipped at the top by the iframe's clipping.
|
1359
|
+
var clientRect1 = rect({
|
1360
|
+
top: -10,
|
1361
|
+
left: 0,
|
1362
|
+
width: bodyWidth,
|
1363
|
+
height: 200
|
1364
|
+
});
|
1365
|
+
var intersectRect1 = rect({
|
1366
|
+
left: 0,
|
1367
|
+
width: bodyWidth,
|
1368
|
+
// Top is clipped.
|
1369
|
+
top: 10,
|
1370
|
+
// The height is less by both: offset and scroll.
|
1371
|
+
height: 200 - 10 - 10
|
1372
|
+
});
|
1373
|
+
expect(rect(records[0].boundingClientRect)).to.eql(clientRect1);
|
1374
|
+
expect(rect(records[0].intersectionRect)).to.eql(intersectRect1);
|
1375
|
+
expect(records[0].isIntersecting).to.be(true);
|
1376
|
+
expect(records[0].intersectionRatio).to.within(0.89, 0.91); // ~0.9
|
1377
|
+
|
1378
|
+
// The target2 is partially visible.
|
1379
|
+
var clientRect2 = rect({
|
1380
|
+
top: 202 - 10,
|
1381
|
+
left: 0,
|
1382
|
+
width: bodyWidth,
|
1383
|
+
height: 200
|
1384
|
+
});
|
1385
|
+
var intersectRect2 = rect({
|
1386
|
+
top: 202 - 10,
|
1387
|
+
left: 0,
|
1388
|
+
width: bodyWidth,
|
1389
|
+
// The bottom is clipped off.
|
1390
|
+
bottom: 300
|
1391
|
+
});
|
1392
|
+
expect(rect(records[1].boundingClientRect)).to.eql(clientRect2);
|
1393
|
+
expect(rect(records[1].intersectionRect)).to.eql(intersectRect2);
|
1394
|
+
expect(records[1].isIntersecting).to.be(true);
|
1395
|
+
expect(records[1].intersectionRatio).to.be.within(0.53, 0.55); // ~0.54
|
1396
|
+
|
1397
|
+
done();
|
1398
|
+
io.disconnect();
|
1399
|
+
});
|
1400
|
+
io.observe(iframeTargetEl1);
|
1401
|
+
io.observe(iframeTargetEl2);
|
1402
|
+
});
|
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
|
+
|
1487
|
+
it('handles style changes', function(done) {
|
1488
|
+
var spy = sinon.spy();
|
1489
|
+
|
1490
|
+
// When first element becomes invisible, the second element will show.
|
1491
|
+
// And in reverse: when the first element becomes visible again, the
|
1492
|
+
// second element will disappear.
|
1493
|
+
var io = new IntersectionObserver(spy);
|
1494
|
+
io.observe(iframeTargetEl1);
|
1495
|
+
io.observe(iframeTargetEl2);
|
1496
|
+
|
1497
|
+
runSequence([
|
1498
|
+
function(done) {
|
1499
|
+
setTimeout(function() {
|
1500
|
+
expect(spy.callCount).to.be(1);
|
1501
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
1502
|
+
expect(records.length).to.be(2);
|
1503
|
+
expect(records[0].intersectionRatio).to.be(1);
|
1504
|
+
expect(records[0].isIntersecting).to.be(true);
|
1505
|
+
expect(records[1].intersectionRatio).to.be(0);
|
1506
|
+
expect(records[1].isIntersecting).to.be(false);
|
1507
|
+
done();
|
1508
|
+
}, ASYNC_TIMEOUT);
|
1509
|
+
},
|
1510
|
+
function(done) {
|
1511
|
+
iframeTargetEl1.style.display = 'none';
|
1512
|
+
setTimeout(function() {
|
1513
|
+
expect(spy.callCount).to.be(2);
|
1514
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
1515
|
+
expect(records.length).to.be(2);
|
1516
|
+
expect(records[0].intersectionRatio).to.be(0);
|
1517
|
+
expect(records[0].isIntersecting).to.be(false);
|
1518
|
+
expect(records[1].intersectionRatio).to.be(1);
|
1519
|
+
expect(records[1].isIntersecting).to.be(true);
|
1520
|
+
done();
|
1521
|
+
}, ASYNC_TIMEOUT);
|
1522
|
+
},
|
1523
|
+
function(done) {
|
1524
|
+
iframeTargetEl1.style.display = '';
|
1525
|
+
setTimeout(function() {
|
1526
|
+
expect(spy.callCount).to.be(3);
|
1527
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
1528
|
+
expect(records.length).to.be(2);
|
1529
|
+
expect(records[0].intersectionRatio).to.be(1);
|
1530
|
+
expect(records[0].isIntersecting).to.be(true);
|
1531
|
+
expect(records[1].intersectionRatio).to.be(0);
|
1532
|
+
expect(records[1].isIntersecting).to.be(false);
|
1533
|
+
done();
|
1534
|
+
}, ASYNC_TIMEOUT);
|
1535
|
+
},
|
1536
|
+
function(done) {
|
1537
|
+
io.disconnect();
|
1538
|
+
done();
|
1539
|
+
}
|
1540
|
+
], done);
|
1541
|
+
});
|
1542
|
+
|
1543
|
+
it('handles scroll changes', function(done) {
|
1544
|
+
var spy = sinon.spy();
|
1545
|
+
|
1546
|
+
// Scrolling to the middle of the iframe shows the second box and
|
1547
|
+
// hides the first.
|
1548
|
+
var io = new IntersectionObserver(spy);
|
1549
|
+
io.observe(iframeTargetEl1);
|
1550
|
+
io.observe(iframeTargetEl2);
|
1551
|
+
|
1552
|
+
runSequence([
|
1553
|
+
function(done) {
|
1554
|
+
setTimeout(function() {
|
1555
|
+
expect(spy.callCount).to.be(1);
|
1556
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
1557
|
+
expect(records.length).to.be(2);
|
1558
|
+
expect(records[0].intersectionRatio).to.be(1);
|
1559
|
+
expect(records[0].isIntersecting).to.be(true);
|
1560
|
+
expect(records[1].intersectionRatio).to.be(0);
|
1561
|
+
expect(records[1].isIntersecting).to.be(false);
|
1562
|
+
done();
|
1563
|
+
}, ASYNC_TIMEOUT);
|
1564
|
+
},
|
1565
|
+
function(done) {
|
1566
|
+
iframeWin.scrollTo(0, 202);
|
1567
|
+
setTimeout(function() {
|
1568
|
+
expect(spy.callCount).to.be(2);
|
1569
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
1570
|
+
expect(records.length).to.be(2);
|
1571
|
+
expect(records[0].intersectionRatio).to.be(0);
|
1572
|
+
expect(records[0].isIntersecting).to.be(false);
|
1573
|
+
expect(records[1].intersectionRatio).to.be(1);
|
1574
|
+
expect(records[1].isIntersecting).to.be(true);
|
1575
|
+
done();
|
1576
|
+
}, ASYNC_TIMEOUT);
|
1577
|
+
},
|
1578
|
+
function(done) {
|
1579
|
+
iframeWin.scrollTo(0, 0);
|
1580
|
+
setTimeout(function() {
|
1581
|
+
expect(spy.callCount).to.be(3);
|
1582
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
1583
|
+
expect(records.length).to.be(2);
|
1584
|
+
expect(records[0].intersectionRatio).to.be(1);
|
1585
|
+
expect(records[0].isIntersecting).to.be(true);
|
1586
|
+
expect(records[1].intersectionRatio).to.be(0);
|
1587
|
+
expect(records[1].isIntersecting).to.be(false);
|
1588
|
+
done();
|
1589
|
+
}, ASYNC_TIMEOUT);
|
1590
|
+
},
|
1591
|
+
function(done) {
|
1592
|
+
io.disconnect();
|
1593
|
+
done();
|
1594
|
+
}
|
1595
|
+
], done);
|
1596
|
+
});
|
1597
|
+
|
1598
|
+
it('handles iframe changes', function(done) {
|
1599
|
+
var spy = sinon.spy();
|
1600
|
+
|
1601
|
+
// Iframe goes off screen and returns.
|
1602
|
+
var io = new IntersectionObserver(spy);
|
1603
|
+
io.observe(iframeTargetEl1);
|
1604
|
+
io.observe(iframeTargetEl2);
|
1605
|
+
|
1606
|
+
runSequence([
|
1607
|
+
function(done) {
|
1608
|
+
setTimeout(function() {
|
1609
|
+
expect(spy.callCount).to.be(1);
|
1610
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
1611
|
+
expect(records.length).to.be(2);
|
1612
|
+
expect(records[0].intersectionRatio).to.be(1);
|
1613
|
+
expect(records[0].isIntersecting).to.be(true);
|
1614
|
+
expect(records[1].intersectionRatio).to.be(0);
|
1615
|
+
expect(records[1].isIntersecting).to.be(false);
|
1616
|
+
// Top-level bounds.
|
1617
|
+
expect(records[0].rootBounds.height).to.be(innerHeight);
|
1618
|
+
expect(records[0].intersectionRect.height).to.be(200);
|
1619
|
+
done();
|
1620
|
+
}, ASYNC_TIMEOUT);
|
1621
|
+
},
|
1622
|
+
function(done) {
|
1623
|
+
// Completely off screen.
|
1624
|
+
iframe.style.top = '-202px';
|
1625
|
+
setTimeout(function() {
|
1626
|
+
expect(spy.callCount).to.be(2);
|
1627
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
1628
|
+
expect(records.length).to.be(1);
|
1629
|
+
expect(records[0].intersectionRatio).to.be(0);
|
1630
|
+
expect(records[0].isIntersecting).to.be(false);
|
1631
|
+
// Top-level bounds.
|
1632
|
+
expect(records[0].rootBounds.height).to.be(innerHeight);
|
1633
|
+
expect(records[0].intersectionRect.height).to.be(0);
|
1634
|
+
done();
|
1635
|
+
}, ASYNC_TIMEOUT);
|
1636
|
+
},
|
1637
|
+
function(done) {
|
1638
|
+
// Partially returns.
|
1639
|
+
iframe.style.top = '-100px';
|
1640
|
+
setTimeout(function() {
|
1641
|
+
expect(spy.callCount).to.be(3);
|
1642
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
1643
|
+
expect(records.length).to.be(1);
|
1644
|
+
expect(records[0].intersectionRatio).to.be.within(0.45, 0.55);
|
1645
|
+
expect(records[0].isIntersecting).to.be(true);
|
1646
|
+
// Top-level bounds.
|
1647
|
+
expect(records[0].rootBounds.height).to.be(innerHeight);
|
1648
|
+
expect(records[0].intersectionRect.height / 200).to.be.within(0.45, 0.55);
|
1649
|
+
done();
|
1650
|
+
}, ASYNC_TIMEOUT);
|
1651
|
+
},
|
1652
|
+
function(done) {
|
1653
|
+
io.disconnect();
|
1654
|
+
done();
|
1655
|
+
}
|
1656
|
+
], done);
|
1657
|
+
});
|
1658
|
+
|
1659
|
+
it('continues to monitor until the last target unobserved', function(done) {
|
1660
|
+
var spy = sinon.spy();
|
1661
|
+
|
1662
|
+
// Iframe goes off screen and returns.
|
1663
|
+
var io = new IntersectionObserver(spy);
|
1664
|
+
io.observe(target1);
|
1665
|
+
io.observe(iframeTargetEl1);
|
1666
|
+
io.observe(iframeTargetEl2);
|
1667
|
+
|
1668
|
+
runSequence([
|
1669
|
+
function(done) {
|
1670
|
+
setTimeout(function() {
|
1671
|
+
expect(spy.callCount).to.be(1);
|
1672
|
+
expect(spy.lastCall.args[0].length).to.be(3);
|
1673
|
+
|
1674
|
+
// Unobserve one from the main context and one from iframe.
|
1675
|
+
io.unobserve(target1);
|
1676
|
+
io.unobserve(iframeTargetEl2);
|
1677
|
+
|
1678
|
+
done();
|
1679
|
+
}, ASYNC_TIMEOUT);
|
1680
|
+
},
|
1681
|
+
function(done) {
|
1682
|
+
// Completely off screen.
|
1683
|
+
iframe.style.top = '-202px';
|
1684
|
+
setTimeout(function() {
|
1685
|
+
expect(spy.callCount).to.be(2);
|
1686
|
+
expect(spy.lastCall.args[0].length).to.be(1);
|
1687
|
+
|
1688
|
+
io.unobserve(iframeTargetEl1);
|
1689
|
+
|
1690
|
+
done();
|
1691
|
+
}, ASYNC_TIMEOUT);
|
1692
|
+
},
|
1693
|
+
function(done) {
|
1694
|
+
// Partially returns.
|
1695
|
+
iframe.style.top = '-100px';
|
1696
|
+
setTimeout(function() {
|
1697
|
+
expect(spy.callCount).to.be(2);
|
1698
|
+
done();
|
1699
|
+
}, ASYNC_TIMEOUT);
|
1700
|
+
},
|
1701
|
+
function(done) {
|
1702
|
+
io.disconnect();
|
1703
|
+
done();
|
1704
|
+
}
|
1705
|
+
], done);
|
1706
|
+
});
|
1707
|
+
});
|
1708
|
+
|
1709
|
+
describe('same-origin iframe loaded in an iframe', function() {
|
1710
|
+
var ASYNC_TIMEOUT = 300;
|
1711
|
+
|
1712
|
+
beforeEach(function(done) {
|
1713
|
+
/* Uncomment these lines to force polyfill inside the iframe.
|
1714
|
+
delete iframeWin.IntersectionObserver;
|
1715
|
+
delete iframeWin.IntersectionObserverEntry;
|
1716
|
+
*/
|
1717
|
+
|
1718
|
+
// Install polyfill right into the iframe.
|
1719
|
+
if (!iframeWin.IntersectionObserver) {
|
1720
|
+
var script = iframeDoc.createElement('script');
|
1721
|
+
script.src = 'intersection-observer.js';
|
1722
|
+
script.onload = function() {
|
1723
|
+
done();
|
1724
|
+
};
|
1725
|
+
iframeDoc.body.appendChild(script);
|
1726
|
+
} else {
|
1727
|
+
done();
|
1728
|
+
}
|
1729
|
+
});
|
1730
|
+
|
1731
|
+
function computeRectIntersection(rect1, rect2) {
|
1732
|
+
var top = Math.max(rect1.top, rect2.top);
|
1733
|
+
var bottom = Math.min(rect1.bottom, rect2.bottom);
|
1734
|
+
var left = Math.max(rect1.left, rect2.left);
|
1735
|
+
var right = Math.min(rect1.right, rect2.right);
|
1736
|
+
var width = right - left;
|
1737
|
+
var height = bottom - top;
|
1738
|
+
|
1739
|
+
return (width >= 0 && height >= 0) && {
|
1740
|
+
top: top,
|
1741
|
+
bottom: bottom,
|
1742
|
+
left: left,
|
1743
|
+
right: right,
|
1744
|
+
width: width,
|
1745
|
+
height: height
|
1746
|
+
} || {
|
1747
|
+
top: 0,
|
1748
|
+
bottom: 0,
|
1749
|
+
left: 0,
|
1750
|
+
right: 0,
|
1751
|
+
width: 0,
|
1752
|
+
height: 0
|
1753
|
+
};
|
1754
|
+
}
|
1755
|
+
|
1756
|
+
function checkRootBounds(records) {
|
1757
|
+
if (!supportsNativeIntersectionObserver(iframeWin)) {
|
1758
|
+
records.forEach(function(record) {
|
1759
|
+
expect(rect(record.rootBounds)).to.eql(getRootRect(document));
|
1760
|
+
});
|
1761
|
+
}
|
1762
|
+
}
|
1763
|
+
|
1764
|
+
function applyParentRect(parentRect) {
|
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';
|
1769
|
+
}
|
1770
|
+
|
1771
|
+
function createObserver(callback, options, parentRect) {
|
1772
|
+
var io = new iframeWin.IntersectionObserver(callback, options);
|
1773
|
+
if (parentRect) {
|
1774
|
+
applyParentRect(parentRect);
|
1775
|
+
}
|
1776
|
+
return io;
|
1777
|
+
}
|
1778
|
+
|
1779
|
+
it('calculates rects for a fully visible frame', function(done) {
|
1780
|
+
var parentRect = rect({top: 0, left: 20, height: 300, width: 100});
|
1781
|
+
var io = createObserver(function(unsortedRecords) {
|
1782
|
+
var records = sortRecords(unsortedRecords);
|
1783
|
+
expect(records.length).to.be(3);
|
1784
|
+
checkRootBounds(records);
|
1785
|
+
|
1786
|
+
// The documentElement is partially visible.
|
1787
|
+
expect(rect(records[0].boundingClientRect))
|
1788
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
1789
|
+
expect(rect(records[0].intersectionRect)).to.eql(rect({
|
1790
|
+
top: 0,
|
1791
|
+
left: 0,
|
1792
|
+
width: bodyWidth,
|
1793
|
+
height: 300
|
1794
|
+
}));
|
1795
|
+
expect(records[0].isIntersecting).to.be(true);
|
1796
|
+
// 300 / 404 == ~0.743
|
1797
|
+
expect(records[0].intersectionRatio).to.be.within(0.74, 0.75);
|
1798
|
+
|
1799
|
+
// The document.body is partially visible.
|
1800
|
+
expect(rect(records[1].boundingClientRect))
|
1801
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
1802
|
+
expect(rect(records[1].intersectionRect)).to.eql(rect({
|
1803
|
+
top: 0,
|
1804
|
+
left: 0,
|
1805
|
+
width: bodyWidth,
|
1806
|
+
height: 300
|
1807
|
+
}));
|
1808
|
+
expect(records[1].isIntersecting).to.be(true);
|
1809
|
+
// 300 / 402 == ~0.746
|
1810
|
+
expect(records[1].intersectionRatio).to.be.within(0.74, 0.75);
|
1811
|
+
|
1812
|
+
// The target1 is fully visible.
|
1813
|
+
var clientRect1 = rect({
|
1814
|
+
top: 0,
|
1815
|
+
left: 0,
|
1816
|
+
width: bodyWidth,
|
1817
|
+
height: 200
|
1818
|
+
});
|
1819
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
1820
|
+
expect(rect(records[2].intersectionRect)).to.eql(clientRect1);
|
1821
|
+
expect(records[2].isIntersecting).to.be(true);
|
1822
|
+
expect(records[2].intersectionRatio).to.be(1);
|
1823
|
+
|
1824
|
+
done();
|
1825
|
+
io.disconnect();
|
1826
|
+
}, {}, parentRect);
|
1827
|
+
io.observe(documentElement);
|
1828
|
+
io.observe(body);
|
1829
|
+
io.observe(iframeTargetEl1);
|
1830
|
+
});
|
1831
|
+
|
1832
|
+
it('calculates rects for a fully visible and offset frame', function(done) {
|
1833
|
+
var parentRect = rect({top: 10, left: 20, height: 300, width: 100});
|
1834
|
+
var io = createObserver(function(unsortedRecords) {
|
1835
|
+
var records = sortRecords(unsortedRecords);
|
1836
|
+
expect(records.length).to.be(3);
|
1837
|
+
checkRootBounds(records);
|
1838
|
+
|
1839
|
+
// The documentElement is partially visible.
|
1840
|
+
expect(rect(records[0].boundingClientRect))
|
1841
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
1842
|
+
expect(rect(records[0].intersectionRect)).to.eql(rect({
|
1843
|
+
top: 0,
|
1844
|
+
left: 0,
|
1845
|
+
width: bodyWidth,
|
1846
|
+
height: 300
|
1847
|
+
}));
|
1848
|
+
expect(records[0].isIntersecting).to.be(true);
|
1849
|
+
// 300 / 404 == ~0.743
|
1850
|
+
expect(records[0].intersectionRatio).to.be.within(0.74, 0.75);
|
1851
|
+
|
1852
|
+
// The document.body is partially visible.
|
1853
|
+
expect(rect(records[1].boundingClientRect))
|
1854
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
1855
|
+
expect(rect(records[1].intersectionRect)).to.eql(rect({
|
1856
|
+
top: 0,
|
1857
|
+
left: 0,
|
1858
|
+
width: bodyWidth,
|
1859
|
+
height: 300
|
1860
|
+
}));
|
1861
|
+
expect(records[1].isIntersecting).to.be(true);
|
1862
|
+
// 300 / 402 == ~0.746
|
1863
|
+
expect(records[1].intersectionRatio).to.be.within(0.74, 0.75);
|
1864
|
+
|
1865
|
+
// The target1 is fully visible.
|
1866
|
+
var clientRect1 = rect({
|
1867
|
+
top: 0,
|
1868
|
+
left: 0,
|
1869
|
+
width: bodyWidth,
|
1870
|
+
height: 200
|
1871
|
+
});
|
1872
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
1873
|
+
expect(rect(records[2].intersectionRect)).to.eql(clientRect1);
|
1874
|
+
expect(records[2].isIntersecting).to.be(true);
|
1875
|
+
expect(records[2].intersectionRatio).to.be(1);
|
1876
|
+
|
1877
|
+
done();
|
1878
|
+
io.disconnect();
|
1879
|
+
}, {}, parentRect);
|
1880
|
+
io.observe(documentElement);
|
1881
|
+
io.observe(body);
|
1882
|
+
io.observe(iframeTargetEl1);
|
1883
|
+
});
|
1884
|
+
|
1885
|
+
it('calculates rects for a clipped frame on top', function(done) {
|
1886
|
+
var parentRect = rect({top: -10, left: 20, height: 300, width: 100});
|
1887
|
+
var io = createObserver(function(unsortedRecords) {
|
1888
|
+
var records = sortRecords(unsortedRecords);
|
1889
|
+
expect(records.length).to.be(3);
|
1890
|
+
checkRootBounds(records);
|
1891
|
+
|
1892
|
+
// The documentElement is partially visible.
|
1893
|
+
expect(rect(records[0].boundingClientRect))
|
1894
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
1895
|
+
expect(rect(records[0].intersectionRect)).to.eql(rect({
|
1896
|
+
top: 10,
|
1897
|
+
left: 0,
|
1898
|
+
width: bodyWidth,
|
1899
|
+
height: 300 - 10
|
1900
|
+
}));
|
1901
|
+
expect(records[0].isIntersecting).to.be(true);
|
1902
|
+
// (300 - 10) / 404 == ~0.717
|
1903
|
+
expect(records[0].intersectionRatio).to.be.within(0.71, 0.72);
|
1904
|
+
|
1905
|
+
// The document.body is partially visible.
|
1906
|
+
expect(rect(records[1].boundingClientRect))
|
1907
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
1908
|
+
expect(rect(records[1].intersectionRect)).to.eql(rect({
|
1909
|
+
top: 10,
|
1910
|
+
left: 0,
|
1911
|
+
width: bodyWidth,
|
1912
|
+
height: 300 - 10
|
1913
|
+
}));
|
1914
|
+
expect(records[1].isIntersecting).to.be(true);
|
1915
|
+
// (300 - 10) / 402 == ~0.721
|
1916
|
+
expect(records[1].intersectionRatio).to.be.within(0.72, 0.73);
|
1917
|
+
|
1918
|
+
// The target1 is clipped at the top by the iframe's clipping.
|
1919
|
+
var clientRect1 = rect({
|
1920
|
+
top: 0,
|
1921
|
+
left: 0,
|
1922
|
+
width: bodyWidth,
|
1923
|
+
height: 200
|
1924
|
+
});
|
1925
|
+
var intersectRect1 = rect({
|
1926
|
+
left: 0,
|
1927
|
+
width: bodyWidth,
|
1928
|
+
// Top is clipped.
|
1929
|
+
top: 10,
|
1930
|
+
height: 200 - 10
|
1931
|
+
});
|
1932
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
1933
|
+
expect(rect(records[2].intersectionRect)).to.eql(intersectRect1);
|
1934
|
+
expect(records[2].isIntersecting).to.be(true);
|
1935
|
+
expect(records[2].intersectionRatio).to.within(0.94, 0.96); // ~0.95
|
1936
|
+
|
1937
|
+
done();
|
1938
|
+
io.disconnect();
|
1939
|
+
}, {}, parentRect);
|
1940
|
+
io.observe(documentElement);
|
1941
|
+
io.observe(body);
|
1942
|
+
io.observe(iframeTargetEl1);
|
1943
|
+
});
|
1944
|
+
|
1945
|
+
it('calculates rects for a clipped frame on bottom', function(done) {
|
1946
|
+
var rootRect = getRootRect(document);
|
1947
|
+
var parentRect = rect({top: rootRect.bottom - 300 + 10, left: 20, height: 300, width: 100});
|
1948
|
+
var io = createObserver(function(unsortedRecords) {
|
1949
|
+
var records = sortRecords(unsortedRecords);
|
1950
|
+
expect(records.length).to.be(3);
|
1951
|
+
checkRootBounds(records);
|
1952
|
+
|
1953
|
+
// The documentElement is partially visible.
|
1954
|
+
expect(rect(records[0].boundingClientRect))
|
1955
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
1956
|
+
expect(rect(records[0].intersectionRect)).to.eql(rect({
|
1957
|
+
top: 0,
|
1958
|
+
left: 0,
|
1959
|
+
width: bodyWidth,
|
1960
|
+
height: 300 - 10
|
1961
|
+
}));
|
1962
|
+
expect(records[0].isIntersecting).to.be(true);
|
1963
|
+
// (300 - 10) / 404 == ~0.717
|
1964
|
+
expect(records[0].intersectionRatio).to.be.within(0.71, 0.72);
|
1965
|
+
|
1966
|
+
// The document.body is partially visible.
|
1967
|
+
expect(rect(records[1].boundingClientRect))
|
1968
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
1969
|
+
expect(rect(records[1].intersectionRect)).to.eql(rect({
|
1970
|
+
top: 0,
|
1971
|
+
left: 0,
|
1972
|
+
width: bodyWidth,
|
1973
|
+
height: 300 - 10
|
1974
|
+
}));
|
1975
|
+
expect(records[1].isIntersecting).to.be(true);
|
1976
|
+
// (300 - 10) / 402 == ~0.721
|
1977
|
+
expect(records[1].intersectionRatio).to.be.within(0.72, 0.73);
|
1978
|
+
|
1979
|
+
// The target1 is clipped at the top by the iframe's clipping.
|
1980
|
+
var clientRect1 = rect({
|
1981
|
+
top: 0,
|
1982
|
+
left: 0,
|
1983
|
+
width: bodyWidth,
|
1984
|
+
height: 200
|
1985
|
+
});
|
1986
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
1987
|
+
expect(rect(records[2].intersectionRect)).to.eql(clientRect1);
|
1988
|
+
expect(records[2].isIntersecting).to.be(true);
|
1989
|
+
expect(records[2].intersectionRatio).to.be(1);
|
1990
|
+
|
1991
|
+
done();
|
1992
|
+
io.disconnect();
|
1993
|
+
}, {}, parentRect);
|
1994
|
+
io.observe(documentElement);
|
1995
|
+
io.observe(body);
|
1996
|
+
io.observe(iframeTargetEl1);
|
1997
|
+
});
|
1998
|
+
|
1999
|
+
it('calculates rects for a fully visible and scrolled frame', function(done) {
|
2000
|
+
iframeWin.scrollTo(0, 10);
|
2001
|
+
var parentRect = rect({top: 0, left: 20, height: 300, width: 100});
|
2002
|
+
var io = createObserver(function(unsortedRecords) {
|
2003
|
+
var records = sortRecords(unsortedRecords);
|
2004
|
+
expect(records.length).to.be(3);
|
2005
|
+
checkRootBounds(records);
|
2006
|
+
|
2007
|
+
// The documentElement is partially visible.
|
2008
|
+
expect(rect(records[0].boundingClientRect))
|
2009
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
2010
|
+
expect(rect(records[0].intersectionRect)).to.eql(rect({
|
2011
|
+
top: 0,
|
2012
|
+
left: 0,
|
2013
|
+
width: bodyWidth,
|
2014
|
+
height: 300
|
2015
|
+
}));
|
2016
|
+
expect(records[0].isIntersecting).to.be(true);
|
2017
|
+
// 300 / 404 == ~0.743
|
2018
|
+
expect(records[0].intersectionRatio).to.be.within(0.74, 0.75);
|
2019
|
+
|
2020
|
+
// The document.body is partially visible.
|
2021
|
+
expect(rect(records[1].boundingClientRect))
|
2022
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
2023
|
+
expect(rect(records[1].intersectionRect)).to.eql(rect({
|
2024
|
+
top: 0,
|
2025
|
+
left: 0,
|
2026
|
+
width: bodyWidth,
|
2027
|
+
height: 300
|
2028
|
+
}));
|
2029
|
+
expect(records[1].isIntersecting).to.be(true);
|
2030
|
+
// 300 / 402 == ~0.746
|
2031
|
+
expect(records[1].intersectionRatio).to.be.within(0.74, 0.75);
|
2032
|
+
|
2033
|
+
// The target1 is fully visible.
|
2034
|
+
var clientRect1 = rect({
|
2035
|
+
top: -10,
|
2036
|
+
left: 0,
|
2037
|
+
width: bodyWidth,
|
2038
|
+
height: 200
|
2039
|
+
});
|
2040
|
+
var intersectRect1 = rect({
|
2041
|
+
top: 0,
|
2042
|
+
left: 0,
|
2043
|
+
width: bodyWidth,
|
2044
|
+
// Height is only for the visible area.
|
2045
|
+
height: 200 - 10
|
2046
|
+
});
|
2047
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
2048
|
+
expect(rect(records[2].intersectionRect)).to.eql(intersectRect1);
|
2049
|
+
expect(records[2].isIntersecting).to.be(true);
|
2050
|
+
expect(records[2].intersectionRatio).to.within(0.94, 0.96); // ~0.95
|
2051
|
+
|
2052
|
+
done();
|
2053
|
+
io.disconnect();
|
2054
|
+
}, {}, parentRect);
|
2055
|
+
io.observe(documentElement);
|
2056
|
+
io.observe(body);
|
2057
|
+
io.observe(iframeTargetEl1);
|
2058
|
+
});
|
2059
|
+
|
2060
|
+
it('calculates rects for a clipped frame on top and scrolled', function(done) {
|
2061
|
+
iframeWin.scrollTo(0, 10);
|
2062
|
+
var parentRect = rect({top: -10, left: 0, height: 300, width: 100});
|
2063
|
+
var io = createObserver(function(unsortedRecords) {
|
2064
|
+
var records = sortRecords(unsortedRecords);
|
2065
|
+
expect(records.length).to.be(4);
|
2066
|
+
checkRootBounds(records);
|
2067
|
+
|
2068
|
+
// The documentElement is partially visible.
|
2069
|
+
expect(rect(records[0].boundingClientRect))
|
2070
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
2071
|
+
expect(rect(records[0].intersectionRect)).to.eql(rect({
|
2072
|
+
top: 10,
|
2073
|
+
left: 0,
|
2074
|
+
width: bodyWidth,
|
2075
|
+
height: 300 - 10
|
2076
|
+
}));
|
2077
|
+
expect(records[0].isIntersecting).to.be(true);
|
2078
|
+
// (300 - 10) / 404 == ~0.717
|
2079
|
+
expect(records[0].intersectionRatio).to.be.within(0.71, 0.72);
|
2080
|
+
|
2081
|
+
// The document.body is partially visible.
|
2082
|
+
expect(rect(records[1].boundingClientRect))
|
2083
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
2084
|
+
expect(rect(records[1].intersectionRect)).to.eql(rect({
|
2085
|
+
top: 10,
|
2086
|
+
left: 0,
|
2087
|
+
width: bodyWidth,
|
2088
|
+
height: 300 - 10
|
2089
|
+
}));
|
2090
|
+
expect(records[1].isIntersecting).to.be(true);
|
2091
|
+
// (300 - 10) / 402 == ~0.721
|
2092
|
+
expect(records[1].intersectionRatio).to.be.within(0.72, 0.73);
|
2093
|
+
|
2094
|
+
// The target1 is clipped at the top by the iframe's clipping.
|
2095
|
+
var clientRect1 = rect({
|
2096
|
+
top: -10,
|
2097
|
+
left: 0,
|
2098
|
+
width: bodyWidth,
|
2099
|
+
height: 200
|
2100
|
+
});
|
2101
|
+
var intersectRect1 = rect({
|
2102
|
+
left: 0,
|
2103
|
+
width: bodyWidth,
|
2104
|
+
// Top is clipped.
|
2105
|
+
top: 10,
|
2106
|
+
// The height is less by both: offset and scroll.
|
2107
|
+
height: 200 - 10 - 10
|
2108
|
+
});
|
2109
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
2110
|
+
expect(rect(records[2].intersectionRect)).to.eql(intersectRect1);
|
2111
|
+
expect(records[2].isIntersecting).to.be(true);
|
2112
|
+
expect(records[2].intersectionRatio).to.within(0.89, 0.91); // ~0.9
|
2113
|
+
|
2114
|
+
// The target2 is partially visible.
|
2115
|
+
var clientRect2 = rect({
|
2116
|
+
top: 202 - 10,
|
2117
|
+
left: 0,
|
2118
|
+
width: bodyWidth,
|
2119
|
+
height: 200
|
2120
|
+
});
|
2121
|
+
var intersectRect2 = rect({
|
2122
|
+
top: 202 - 10,
|
2123
|
+
left: 0,
|
2124
|
+
width: bodyWidth,
|
2125
|
+
// The bottom is clipped off.
|
2126
|
+
bottom: 300
|
2127
|
+
});
|
2128
|
+
expect(rect(records[3].boundingClientRect)).to.eql(clientRect2);
|
2129
|
+
expect(rect(records[3].intersectionRect)).to.eql(intersectRect2);
|
2130
|
+
expect(records[3].isIntersecting).to.be(true);
|
2131
|
+
expect(records[3].intersectionRatio).to.be.within(0.53, 0.55); // ~0.54
|
2132
|
+
|
2133
|
+
done();
|
2134
|
+
io.disconnect();
|
2135
|
+
}, {}, parentRect);
|
2136
|
+
io.observe(documentElement);
|
2137
|
+
io.observe(body);
|
2138
|
+
io.observe(iframeTargetEl1);
|
2139
|
+
io.observe(iframeTargetEl2);
|
2140
|
+
});
|
2141
|
+
|
2142
|
+
it('calculates rects for a fully clipped frame', function(done) {
|
2143
|
+
var parentRect = rect({top: -400, left: 20, height: 300, width: 100});
|
2144
|
+
var io = createObserver(function(unsortedRecords) {
|
2145
|
+
var records = sortRecords(unsortedRecords);
|
2146
|
+
expect(records.length).to.be(3);
|
2147
|
+
checkRootBounds(records);
|
2148
|
+
|
2149
|
+
var emptyRect = rect({
|
2150
|
+
top: 0,
|
2151
|
+
left: 0,
|
2152
|
+
width: 0,
|
2153
|
+
height: 0
|
2154
|
+
});
|
2155
|
+
|
2156
|
+
// The documentElement is completely invisible.
|
2157
|
+
expect(rect(records[0].boundingClientRect))
|
2158
|
+
.to.eql(rect(documentElement.getBoundingClientRect()));
|
2159
|
+
expect(rect(records[0].intersectionRect)).to.eql(emptyRect);
|
2160
|
+
expect(records[0].isIntersecting).to.be(false);
|
2161
|
+
expect(records[0].intersectionRatio).to.be(0);
|
2162
|
+
|
2163
|
+
// The document.body is completely invisible.
|
2164
|
+
expect(rect(records[1].boundingClientRect))
|
2165
|
+
.to.eql(rect(body.getBoundingClientRect()));
|
2166
|
+
expect(rect(records[1].intersectionRect)).to.eql(emptyRect);
|
2167
|
+
expect(records[1].isIntersecting).to.be(false);
|
2168
|
+
expect(records[1].intersectionRatio).to.be(0);
|
2169
|
+
|
2170
|
+
// The target1 is completely invisible.
|
2171
|
+
var clientRect1 = rect({
|
2172
|
+
top: 0,
|
2173
|
+
left: 0,
|
2174
|
+
width: bodyWidth,
|
2175
|
+
height: 200
|
2176
|
+
});
|
2177
|
+
expect(rect(records[2].boundingClientRect)).to.eql(clientRect1);
|
2178
|
+
expect(rect(records[2].intersectionRect)).to.eql(emptyRect);
|
2179
|
+
expect(records[2].isIntersecting).to.be(false);
|
2180
|
+
expect(records[2].intersectionRatio).to.be(0);
|
2181
|
+
|
2182
|
+
done();
|
2183
|
+
io.disconnect();
|
2184
|
+
}, {}, parentRect);
|
2185
|
+
io.observe(documentElement);
|
2186
|
+
io.observe(body);
|
2187
|
+
io.observe(iframeTargetEl1);
|
2188
|
+
});
|
2189
|
+
|
2190
|
+
it('handles style changes', function(done) {
|
2191
|
+
var spy = sinon.spy();
|
2192
|
+
|
2193
|
+
var parentRect = rect({top: 0, left: 0, height: 200, width: 100});
|
2194
|
+
|
2195
|
+
// When first element becomes invisible, the second element will show.
|
2196
|
+
// And in reverse: when the first element becomes visible again, the
|
2197
|
+
// second element will disappear.
|
2198
|
+
var io = createObserver(spy, {}, parentRect);
|
2199
|
+
io.observe(iframeTargetEl1);
|
2200
|
+
io.observe(iframeTargetEl2);
|
2201
|
+
|
2202
|
+
runSequence([
|
2203
|
+
function(done) {
|
2204
|
+
setTimeout(function() {
|
2205
|
+
expect(spy.callCount).to.be(1);
|
2206
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2207
|
+
expect(records.length).to.be(2);
|
2208
|
+
expect(records[0].intersectionRatio).to.be(1);
|
2209
|
+
expect(records[0].isIntersecting).to.be(true);
|
2210
|
+
expect(records[1].intersectionRatio).to.be(0);
|
2211
|
+
expect(records[1].isIntersecting).to.be(false);
|
2212
|
+
done();
|
2213
|
+
}, ASYNC_TIMEOUT);
|
2214
|
+
},
|
2215
|
+
function(done) {
|
2216
|
+
iframeTargetEl1.style.display = 'none';
|
2217
|
+
setTimeout(function() {
|
2218
|
+
expect(spy.callCount).to.be(2);
|
2219
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2220
|
+
expect(records.length).to.be(2);
|
2221
|
+
expect(records[0].intersectionRatio).to.be(0);
|
2222
|
+
expect(records[0].isIntersecting).to.be(false);
|
2223
|
+
expect(records[1].intersectionRatio).to.be(1);
|
2224
|
+
expect(records[1].isIntersecting).to.be(true);
|
2225
|
+
done();
|
2226
|
+
}, ASYNC_TIMEOUT);
|
2227
|
+
},
|
2228
|
+
function(done) {
|
2229
|
+
iframeTargetEl1.style.display = '';
|
2230
|
+
setTimeout(function() {
|
2231
|
+
expect(spy.callCount).to.be(3);
|
2232
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2233
|
+
expect(records.length).to.be(2);
|
2234
|
+
expect(records[0].intersectionRatio).to.be(1);
|
2235
|
+
expect(records[0].isIntersecting).to.be(true);
|
2236
|
+
expect(records[1].intersectionRatio).to.be(0);
|
2237
|
+
expect(records[1].isIntersecting).to.be(false);
|
2238
|
+
done();
|
2239
|
+
}, ASYNC_TIMEOUT);
|
2240
|
+
},
|
2241
|
+
function(done) {
|
2242
|
+
io.disconnect();
|
2243
|
+
done();
|
2244
|
+
}
|
2245
|
+
], done);
|
2246
|
+
});
|
2247
|
+
|
2248
|
+
it('handles scroll changes', function(done) {
|
2249
|
+
var spy = sinon.spy();
|
2250
|
+
|
2251
|
+
var parentRect = rect({top: 0, left: 0, height: 200, width: 100});
|
2252
|
+
|
2253
|
+
// Scrolling to the middle of the iframe shows the second box and
|
2254
|
+
// hides the first.
|
2255
|
+
var io = createObserver(spy, {}, parentRect);
|
2256
|
+
io.observe(iframeTargetEl1);
|
2257
|
+
io.observe(iframeTargetEl2);
|
2258
|
+
|
2259
|
+
runSequence([
|
2260
|
+
function(done) {
|
2261
|
+
setTimeout(function() {
|
2262
|
+
expect(spy.callCount).to.be(1);
|
2263
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2264
|
+
expect(records.length).to.be(2);
|
2265
|
+
expect(records[0].intersectionRatio).to.be(1);
|
2266
|
+
expect(records[0].isIntersecting).to.be(true);
|
2267
|
+
expect(records[1].intersectionRatio).to.be(0);
|
2268
|
+
expect(records[1].isIntersecting).to.be(false);
|
2269
|
+
done();
|
2270
|
+
}, ASYNC_TIMEOUT);
|
2271
|
+
},
|
2272
|
+
function(done) {
|
2273
|
+
iframeWin.scrollTo(0, 202);
|
2274
|
+
setTimeout(function() {
|
2275
|
+
expect(spy.callCount).to.be(2);
|
2276
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2277
|
+
expect(records.length).to.be(2);
|
2278
|
+
expect(records[0].intersectionRatio).to.be(0);
|
2279
|
+
expect(records[0].isIntersecting).to.be(false);
|
2280
|
+
expect(records[1].intersectionRatio).to.be(1);
|
2281
|
+
expect(records[1].isIntersecting).to.be(true);
|
2282
|
+
done();
|
2283
|
+
}, ASYNC_TIMEOUT);
|
2284
|
+
},
|
2285
|
+
function(done) {
|
2286
|
+
iframeWin.scrollTo(0, 0);
|
2287
|
+
setTimeout(function() {
|
2288
|
+
expect(spy.callCount).to.be(3);
|
2289
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2290
|
+
expect(records.length).to.be(2);
|
2291
|
+
expect(records[0].intersectionRatio).to.be(1);
|
2292
|
+
expect(records[0].isIntersecting).to.be(true);
|
2293
|
+
expect(records[1].intersectionRatio).to.be(0);
|
2294
|
+
expect(records[1].isIntersecting).to.be(false);
|
2295
|
+
done();
|
2296
|
+
}, ASYNC_TIMEOUT);
|
2297
|
+
},
|
2298
|
+
function(done) {
|
2299
|
+
io.disconnect();
|
2300
|
+
done();
|
2301
|
+
}
|
2302
|
+
], done);
|
2303
|
+
});
|
2304
|
+
|
2305
|
+
it('handles parent rect changes', function(done) {
|
2306
|
+
var spy = sinon.spy();
|
2307
|
+
|
2308
|
+
var parentRect = rect({top: 0, left: 0, height: 200, width: 100});
|
2309
|
+
|
2310
|
+
// Iframe goes off screen and returns.
|
2311
|
+
var io = createObserver(spy, {}, parentRect);
|
2312
|
+
io.observe(iframeTargetEl1);
|
2313
|
+
io.observe(iframeTargetEl2);
|
2314
|
+
|
2315
|
+
runSequence([
|
2316
|
+
function(done) {
|
2317
|
+
setTimeout(function() {
|
2318
|
+
expect(spy.callCount).to.be(1);
|
2319
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2320
|
+
expect(records.length).to.be(2);
|
2321
|
+
checkRootBounds(records);
|
2322
|
+
expect(records[0].intersectionRatio).to.be(1);
|
2323
|
+
expect(records[0].isIntersecting).to.be(true);
|
2324
|
+
expect(records[1].intersectionRatio).to.be(0);
|
2325
|
+
expect(records[1].isIntersecting).to.be(false);
|
2326
|
+
// Top-level bounds.
|
2327
|
+
expect(records[0].intersectionRect.height).to.be(200);
|
2328
|
+
done();
|
2329
|
+
}, ASYNC_TIMEOUT);
|
2330
|
+
},
|
2331
|
+
function(done) {
|
2332
|
+
// Completely off screen.
|
2333
|
+
applyParentRect(rect({top: -202, left: 0, height: 200, width: 100}));
|
2334
|
+
setTimeout(function() {
|
2335
|
+
expect(spy.callCount).to.be(2);
|
2336
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2337
|
+
expect(records.length).to.be(1);
|
2338
|
+
checkRootBounds(records);
|
2339
|
+
expect(records[0].intersectionRatio).to.be(0);
|
2340
|
+
expect(records[0].isIntersecting).to.be(false);
|
2341
|
+
// Top-level bounds.
|
2342
|
+
expect(records[0].intersectionRect.height).to.be(0);
|
2343
|
+
done();
|
2344
|
+
}, ASYNC_TIMEOUT);
|
2345
|
+
},
|
2346
|
+
function(done) {
|
2347
|
+
// Partially returns.
|
2348
|
+
applyParentRect(rect({top: -100, left: 0, height: 200, width: 100}));
|
2349
|
+
setTimeout(function() {
|
2350
|
+
expect(spy.callCount).to.be(3);
|
2351
|
+
var records = sortRecords(spy.lastCall.args[0]);
|
2352
|
+
expect(records.length).to.be(1);
|
2353
|
+
checkRootBounds(records);
|
2354
|
+
expect(records[0].intersectionRatio).to.be.within(0.45, 0.55);
|
2355
|
+
expect(records[0].isIntersecting).to.be(true);
|
2356
|
+
// Top-level bounds.
|
2357
|
+
expect(records[0].intersectionRect.height / 200).to.be.within(0.45, 0.55);
|
2358
|
+
done();
|
2359
|
+
}, ASYNC_TIMEOUT);
|
2360
|
+
},
|
2361
|
+
function(done) {
|
2362
|
+
io.disconnect();
|
2363
|
+
done();
|
2364
|
+
}
|
2365
|
+
], done);
|
2366
|
+
});
|
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
|
+
});
|
3168
|
+
});
|
920
3169
|
});
|
921
3170
|
|
922
3171
|
|
@@ -943,11 +3192,13 @@ function runSequence(functions, done) {
|
|
943
3192
|
/**
|
944
3193
|
* Returns whether or not the current browser has native support for
|
945
3194
|
* IntersectionObserver.
|
3195
|
+
* @param {Window=} win
|
946
3196
|
* @return {boolean} True if native support is detected.
|
947
3197
|
*/
|
948
|
-
function supportsNativeIntersectionObserver() {
|
949
|
-
|
950
|
-
|
3198
|
+
function supportsNativeIntersectionObserver(win) {
|
3199
|
+
win = win || window;
|
3200
|
+
return 'IntersectionObserver' in win &&
|
3201
|
+
win.IntersectionObserver.toString().indexOf('[native code]') > -1;
|
951
3202
|
}
|
952
3203
|
|
953
3204
|
|