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
package/intersection-observer.js
CHANGED
@@ -6,12 +6,15 @@
|
|
6
6
|
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
|
7
7
|
*
|
8
8
|
*/
|
9
|
-
|
10
|
-
(function(window, document) {
|
9
|
+
(function() {
|
11
10
|
'use strict';
|
12
11
|
|
12
|
+
// Exit early if we're not running in a browser.
|
13
|
+
if (typeof window !== 'object') {
|
14
|
+
return;
|
15
|
+
}
|
13
16
|
|
14
|
-
//
|
17
|
+
// Exit early if all IntersectionObserver and IntersectionObserverEntry
|
15
18
|
// features are natively supported.
|
16
19
|
if ('IntersectionObserver' in window &&
|
17
20
|
'IntersectionObserverEntry' in window &&
|
@@ -30,6 +33,32 @@ if ('IntersectionObserver' in window &&
|
|
30
33
|
return;
|
31
34
|
}
|
32
35
|
|
36
|
+
/**
|
37
|
+
* Returns the embedding frame element, if any.
|
38
|
+
* @param {!Document} doc
|
39
|
+
* @return {!Element}
|
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
|
+
}
|
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);
|
33
62
|
|
34
63
|
/**
|
35
64
|
* An IntersectionObserver registry. This registry exists to hold a strong
|
@@ -39,6 +68,19 @@ if ('IntersectionObserver' in window &&
|
|
39
68
|
*/
|
40
69
|
var registry = [];
|
41
70
|
|
71
|
+
/**
|
72
|
+
* The signal updater for cross-origin intersection. When not null, it means
|
73
|
+
* that the polyfill is configured to work in a cross-origin mode.
|
74
|
+
* @type {function(DOMRect|ClientRect, DOMRect|ClientRect)}
|
75
|
+
*/
|
76
|
+
var crossOriginUpdater = null;
|
77
|
+
|
78
|
+
/**
|
79
|
+
* The current cross-origin intersection. Only used in the cross-origin mode.
|
80
|
+
* @type {DOMRect|ClientRect}
|
81
|
+
*/
|
82
|
+
var crossOriginRect = null;
|
83
|
+
|
42
84
|
|
43
85
|
/**
|
44
86
|
* Creates the global IntersectionObserverEntry constructor.
|
@@ -49,9 +91,9 @@ var registry = [];
|
|
49
91
|
function IntersectionObserverEntry(entry) {
|
50
92
|
this.time = entry.time;
|
51
93
|
this.target = entry.target;
|
52
|
-
this.rootBounds = entry.rootBounds;
|
53
|
-
this.boundingClientRect = entry.boundingClientRect;
|
54
|
-
this.intersectionRect = entry.intersectionRect || getEmptyRect();
|
94
|
+
this.rootBounds = ensureDOMRect(entry.rootBounds);
|
95
|
+
this.boundingClientRect = ensureDOMRect(entry.boundingClientRect);
|
96
|
+
this.intersectionRect = ensureDOMRect(entry.intersectionRect || getEmptyRect());
|
55
97
|
this.isIntersecting = !!entry.intersectionRect;
|
56
98
|
|
57
99
|
// Calculates the intersection ratio.
|
@@ -89,8 +131,12 @@ function IntersectionObserver(callback, opt_options) {
|
|
89
131
|
throw new Error('callback must be a function');
|
90
132
|
}
|
91
133
|
|
92
|
-
if (
|
93
|
-
|
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');
|
94
140
|
}
|
95
141
|
|
96
142
|
// Binds and throttles `this._checkForIntersections`.
|
@@ -109,6 +155,11 @@ function IntersectionObserver(callback, opt_options) {
|
|
109
155
|
this.rootMargin = this._rootMarginValues.map(function(margin) {
|
110
156
|
return margin.value + margin.unit;
|
111
157
|
}).join(' ');
|
158
|
+
|
159
|
+
/** @private @const {!Array<!Document>} */
|
160
|
+
this._monitoringDocuments = [];
|
161
|
+
/** @private @const {!Array<function()>} */
|
162
|
+
this._monitoringUnsubscribes = [];
|
112
163
|
}
|
113
164
|
|
114
165
|
|
@@ -133,6 +184,45 @@ IntersectionObserver.prototype.POLL_INTERVAL = null;
|
|
133
184
|
IntersectionObserver.prototype.USE_MUTATION_OBSERVER = true;
|
134
185
|
|
135
186
|
|
187
|
+
/**
|
188
|
+
* Sets up the polyfill in the cross-origin mode. The result is the
|
189
|
+
* updater function that accepts two arguments: `boundingClientRect` and
|
190
|
+
* `intersectionRect` - just as these fields would be available to the
|
191
|
+
* parent via `IntersectionObserverEntry`. This function should be called
|
192
|
+
* each time the iframe receives intersection information from the parent
|
193
|
+
* window, e.g. via messaging.
|
194
|
+
* @return {function(DOMRect|ClientRect, DOMRect|ClientRect)}
|
195
|
+
*/
|
196
|
+
IntersectionObserver._setupCrossOriginUpdater = function() {
|
197
|
+
if (!crossOriginUpdater) {
|
198
|
+
/**
|
199
|
+
* @param {DOMRect|ClientRect} boundingClientRect
|
200
|
+
* @param {DOMRect|ClientRect} intersectionRect
|
201
|
+
*/
|
202
|
+
crossOriginUpdater = function(boundingClientRect, intersectionRect) {
|
203
|
+
if (!boundingClientRect || !intersectionRect) {
|
204
|
+
crossOriginRect = getEmptyRect();
|
205
|
+
} else {
|
206
|
+
crossOriginRect = convertFromParentRect(boundingClientRect, intersectionRect);
|
207
|
+
}
|
208
|
+
registry.forEach(function(observer) {
|
209
|
+
observer._checkForIntersections();
|
210
|
+
});
|
211
|
+
};
|
212
|
+
}
|
213
|
+
return crossOriginUpdater;
|
214
|
+
};
|
215
|
+
|
216
|
+
|
217
|
+
/**
|
218
|
+
* Resets the cross-origin mode.
|
219
|
+
*/
|
220
|
+
IntersectionObserver._resetCrossOriginUpdater = function() {
|
221
|
+
crossOriginUpdater = null;
|
222
|
+
crossOriginRect = null;
|
223
|
+
};
|
224
|
+
|
225
|
+
|
136
226
|
/**
|
137
227
|
* Starts observing a target element for intersection changes based on
|
138
228
|
* the thresholds values.
|
@@ -153,7 +243,7 @@ IntersectionObserver.prototype.observe = function(target) {
|
|
153
243
|
|
154
244
|
this._registerInstance();
|
155
245
|
this._observationTargets.push({element: target, entry: null});
|
156
|
-
this._monitorIntersections();
|
246
|
+
this._monitorIntersections(target.ownerDocument);
|
157
247
|
this._checkForIntersections();
|
158
248
|
};
|
159
249
|
|
@@ -165,11 +255,10 @@ IntersectionObserver.prototype.observe = function(target) {
|
|
165
255
|
IntersectionObserver.prototype.unobserve = function(target) {
|
166
256
|
this._observationTargets =
|
167
257
|
this._observationTargets.filter(function(item) {
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
if (
|
172
|
-
this._unmonitorIntersections();
|
258
|
+
return item.element != target;
|
259
|
+
});
|
260
|
+
this._unmonitorIntersections(target.ownerDocument);
|
261
|
+
if (this._observationTargets.length == 0) {
|
173
262
|
this._unregisterInstance();
|
174
263
|
}
|
175
264
|
};
|
@@ -180,7 +269,7 @@ IntersectionObserver.prototype.unobserve = function(target) {
|
|
180
269
|
*/
|
181
270
|
IntersectionObserver.prototype.disconnect = function() {
|
182
271
|
this._observationTargets = [];
|
183
|
-
this.
|
272
|
+
this._unmonitorAllIntersections();
|
184
273
|
this._unregisterInstance();
|
185
274
|
};
|
186
275
|
|
@@ -253,31 +342,69 @@ IntersectionObserver.prototype._parseRootMargin = function(opt_rootMargin) {
|
|
253
342
|
/**
|
254
343
|
* Starts polling for intersection changes if the polling is not already
|
255
344
|
* happening, and if the page's visibility state is visible.
|
345
|
+
* @param {!Document} doc
|
256
346
|
* @private
|
257
347
|
*/
|
258
|
-
IntersectionObserver.prototype._monitorIntersections = function() {
|
259
|
-
|
260
|
-
|
348
|
+
IntersectionObserver.prototype._monitorIntersections = function(doc) {
|
349
|
+
var win = doc.defaultView;
|
350
|
+
if (!win) {
|
351
|
+
// Already destroyed.
|
352
|
+
return;
|
353
|
+
}
|
354
|
+
if (this._monitoringDocuments.indexOf(doc) != -1) {
|
355
|
+
// Already monitoring.
|
356
|
+
return;
|
357
|
+
}
|
261
358
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
359
|
+
// Private state for monitoring.
|
360
|
+
var callback = this._checkForIntersections;
|
361
|
+
var monitoringInterval = null;
|
362
|
+
var domObserver = null;
|
363
|
+
|
364
|
+
// If a poll interval is set, use polling instead of listening to
|
365
|
+
// resize and scroll events or DOM mutations.
|
366
|
+
if (this.POLL_INTERVAL) {
|
367
|
+
monitoringInterval = win.setInterval(callback, this.POLL_INTERVAL);
|
368
|
+
} else {
|
369
|
+
addEvent(win, 'resize', callback, true);
|
370
|
+
addEvent(doc, 'scroll', callback, true);
|
371
|
+
if (this.USE_MUTATION_OBSERVER && 'MutationObserver' in win) {
|
372
|
+
domObserver = new win.MutationObserver(callback);
|
373
|
+
domObserver.observe(doc, {
|
374
|
+
attributes: true,
|
375
|
+
childList: true,
|
376
|
+
characterData: true,
|
377
|
+
subtree: true
|
378
|
+
});
|
267
379
|
}
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
});
|
380
|
+
}
|
381
|
+
|
382
|
+
this._monitoringDocuments.push(doc);
|
383
|
+
this._monitoringUnsubscribes.push(function() {
|
384
|
+
// Get the window object again. When a friendly iframe is destroyed, it
|
385
|
+
// will be null.
|
386
|
+
var win = doc.defaultView;
|
387
|
+
|
388
|
+
if (win) {
|
389
|
+
if (monitoringInterval) {
|
390
|
+
win.clearInterval(monitoringInterval);
|
280
391
|
}
|
392
|
+
removeEvent(win, 'resize', callback, true);
|
393
|
+
}
|
394
|
+
|
395
|
+
removeEvent(doc, 'scroll', callback, true);
|
396
|
+
if (domObserver) {
|
397
|
+
domObserver.disconnect();
|
398
|
+
}
|
399
|
+
});
|
400
|
+
|
401
|
+
// Also monitor the parent.
|
402
|
+
var rootDoc =
|
403
|
+
(this.root && (this.root.ownerDocument || this.root)) || document;
|
404
|
+
if (doc != rootDoc) {
|
405
|
+
var frame = getFrameElement(doc);
|
406
|
+
if (frame) {
|
407
|
+
this._monitorIntersections(frame.ownerDocument);
|
281
408
|
}
|
282
409
|
}
|
283
410
|
};
|
@@ -285,26 +412,71 @@ IntersectionObserver.prototype._monitorIntersections = function() {
|
|
285
412
|
|
286
413
|
/**
|
287
414
|
* Stops polling for intersection changes.
|
415
|
+
* @param {!Document} doc
|
288
416
|
* @private
|
289
417
|
*/
|
290
|
-
IntersectionObserver.prototype._unmonitorIntersections = function() {
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
this._monitoringInterval = null;
|
418
|
+
IntersectionObserver.prototype._unmonitorIntersections = function(doc) {
|
419
|
+
var index = this._monitoringDocuments.indexOf(doc);
|
420
|
+
if (index == -1) {
|
421
|
+
return;
|
422
|
+
}
|
296
423
|
|
297
|
-
|
298
|
-
|
424
|
+
var rootDoc =
|
425
|
+
(this.root && (this.root.ownerDocument || this.root)) || document;
|
426
|
+
|
427
|
+
// Check if any dependent targets are still remaining.
|
428
|
+
var hasDependentTargets =
|
429
|
+
this._observationTargets.some(function(item) {
|
430
|
+
var itemDoc = item.element.ownerDocument;
|
431
|
+
// Target is in this context.
|
432
|
+
if (itemDoc == doc) {
|
433
|
+
return true;
|
434
|
+
}
|
435
|
+
// Target is nested in this context.
|
436
|
+
while (itemDoc && itemDoc != rootDoc) {
|
437
|
+
var frame = getFrameElement(itemDoc);
|
438
|
+
itemDoc = frame && frame.ownerDocument;
|
439
|
+
if (itemDoc == doc) {
|
440
|
+
return true;
|
441
|
+
}
|
442
|
+
}
|
443
|
+
return false;
|
444
|
+
});
|
445
|
+
if (hasDependentTargets) {
|
446
|
+
return;
|
447
|
+
}
|
299
448
|
|
300
|
-
|
301
|
-
|
302
|
-
|
449
|
+
// Unsubscribe.
|
450
|
+
var unsubscribe = this._monitoringUnsubscribes[index];
|
451
|
+
this._monitoringDocuments.splice(index, 1);
|
452
|
+
this._monitoringUnsubscribes.splice(index, 1);
|
453
|
+
unsubscribe();
|
454
|
+
|
455
|
+
// Also unmonitor the parent.
|
456
|
+
if (doc != rootDoc) {
|
457
|
+
var frame = getFrameElement(doc);
|
458
|
+
if (frame) {
|
459
|
+
this._unmonitorIntersections(frame.ownerDocument);
|
303
460
|
}
|
304
461
|
}
|
305
462
|
};
|
306
463
|
|
307
464
|
|
465
|
+
/**
|
466
|
+
* Stops polling for intersection changes.
|
467
|
+
* @param {!Document} doc
|
468
|
+
* @private
|
469
|
+
*/
|
470
|
+
IntersectionObserver.prototype._unmonitorAllIntersections = function() {
|
471
|
+
var unsubscribes = this._monitoringUnsubscribes.slice(0);
|
472
|
+
this._monitoringDocuments.length = 0;
|
473
|
+
this._monitoringUnsubscribes.length = 0;
|
474
|
+
for (var i = 0; i < unsubscribes.length; i++) {
|
475
|
+
unsubscribes[i]();
|
476
|
+
}
|
477
|
+
};
|
478
|
+
|
479
|
+
|
308
480
|
/**
|
309
481
|
* Scans each observation target for intersection changes and adds them
|
310
482
|
* to the internal entries queue. If new entries are found, it
|
@@ -312,6 +484,11 @@ IntersectionObserver.prototype._unmonitorIntersections = function() {
|
|
312
484
|
* @private
|
313
485
|
*/
|
314
486
|
IntersectionObserver.prototype._checkForIntersections = function() {
|
487
|
+
if (!this.root && crossOriginUpdater && !crossOriginRect) {
|
488
|
+
// Cross origin monitoring, but no initial data available yet.
|
489
|
+
return;
|
490
|
+
}
|
491
|
+
|
315
492
|
var rootIsInDom = this._rootIsInDom();
|
316
493
|
var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect();
|
317
494
|
|
@@ -321,13 +498,20 @@ IntersectionObserver.prototype._checkForIntersections = function() {
|
|
321
498
|
var rootContainsTarget = this._rootContainsTarget(target);
|
322
499
|
var oldEntry = item.entry;
|
323
500
|
var intersectionRect = rootIsInDom && rootContainsTarget &&
|
324
|
-
this._computeTargetAndRootIntersection(target, rootRect);
|
501
|
+
this._computeTargetAndRootIntersection(target, targetRect, rootRect);
|
502
|
+
|
503
|
+
var rootBounds = null;
|
504
|
+
if (!this._rootContainsTarget(target)) {
|
505
|
+
rootBounds = getEmptyRect();
|
506
|
+
} else if (!crossOriginUpdater || this.root) {
|
507
|
+
rootBounds = rootRect;
|
508
|
+
}
|
325
509
|
|
326
510
|
var newEntry = item.entry = new IntersectionObserverEntry({
|
327
511
|
time: now(),
|
328
512
|
target: target,
|
329
513
|
boundingClientRect: targetRect,
|
330
|
-
rootBounds:
|
514
|
+
rootBounds: rootBounds,
|
331
515
|
intersectionRect: intersectionRect
|
332
516
|
});
|
333
517
|
|
@@ -361,6 +545,7 @@ IntersectionObserver.prototype._checkForIntersections = function() {
|
|
361
545
|
* TODO(philipwalton): at this time clip-path is not considered.
|
362
546
|
* https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo
|
363
547
|
* @param {Element} target The target DOM element
|
548
|
+
* @param {Object} targetRect The bounding rect of the target.
|
364
549
|
* @param {Object} rootRect The bounding rect of the root after being
|
365
550
|
* expanded by the rootMargin value.
|
366
551
|
* @return {?Object} The final intersection rect object or undefined if no
|
@@ -368,34 +553,61 @@ IntersectionObserver.prototype._checkForIntersections = function() {
|
|
368
553
|
* @private
|
369
554
|
*/
|
370
555
|
IntersectionObserver.prototype._computeTargetAndRootIntersection =
|
371
|
-
function(target, rootRect) {
|
372
|
-
|
556
|
+
function(target, targetRect, rootRect) {
|
373
557
|
// If the element isn't displayed, an intersection can't happen.
|
374
558
|
if (window.getComputedStyle(target).display == 'none') return;
|
375
559
|
|
376
|
-
var targetRect = getBoundingClientRect(target);
|
377
560
|
var intersectionRect = targetRect;
|
378
561
|
var parent = getParentNode(target);
|
379
562
|
var atRoot = false;
|
380
563
|
|
381
|
-
while (!atRoot) {
|
564
|
+
while (!atRoot && parent) {
|
382
565
|
var parentRect = null;
|
383
566
|
var parentComputedStyle = parent.nodeType == 1 ?
|
384
567
|
window.getComputedStyle(parent) : {};
|
385
568
|
|
386
569
|
// If the parent isn't displayed, an intersection can't happen.
|
387
|
-
if (parentComputedStyle.display == 'none') return;
|
570
|
+
if (parentComputedStyle.display == 'none') return null;
|
388
571
|
|
389
|
-
if (parent == this.root || parent ==
|
572
|
+
if (parent == this.root || parent.nodeType == /* DOCUMENT */ 9) {
|
390
573
|
atRoot = true;
|
391
|
-
|
574
|
+
if (parent == this.root || parent == document) {
|
575
|
+
if (crossOriginUpdater && !this.root) {
|
576
|
+
if (!crossOriginRect ||
|
577
|
+
crossOriginRect.width == 0 && crossOriginRect.height == 0) {
|
578
|
+
// A 0-size cross-origin intersection means no-intersection.
|
579
|
+
parent = null;
|
580
|
+
parentRect = null;
|
581
|
+
intersectionRect = null;
|
582
|
+
} else {
|
583
|
+
parentRect = crossOriginRect;
|
584
|
+
}
|
585
|
+
} else {
|
586
|
+
parentRect = rootRect;
|
587
|
+
}
|
588
|
+
} else {
|
589
|
+
// Check if there's a frame that can be navigated to.
|
590
|
+
var frame = getParentNode(parent);
|
591
|
+
var frameRect = frame && getBoundingClientRect(frame);
|
592
|
+
var frameIntersect =
|
593
|
+
frame &&
|
594
|
+
this._computeTargetAndRootIntersection(frame, frameRect, rootRect);
|
595
|
+
if (frameRect && frameIntersect) {
|
596
|
+
parent = frame;
|
597
|
+
parentRect = convertFromParentRect(frameRect, frameIntersect);
|
598
|
+
} else {
|
599
|
+
parent = null;
|
600
|
+
intersectionRect = null;
|
601
|
+
}
|
602
|
+
}
|
392
603
|
} else {
|
393
604
|
// If the element has a non-visible overflow, and it's not the <body>
|
394
605
|
// or <html> element, update the intersection rect.
|
395
606
|
// Note: <body> and <html> cannot be clipped to a rect that's not also
|
396
607
|
// the document rect, so no need to compute a new intersection.
|
397
|
-
|
398
|
-
|
608
|
+
var doc = parent.ownerDocument;
|
609
|
+
if (parent != doc.body &&
|
610
|
+
parent != doc.documentElement &&
|
399
611
|
parentComputedStyle.overflow != 'visible') {
|
400
612
|
parentRect = getBoundingClientRect(parent);
|
401
613
|
}
|
@@ -405,10 +617,9 @@ IntersectionObserver.prototype._computeTargetAndRootIntersection =
|
|
405
617
|
// calculate new intersection data.
|
406
618
|
if (parentRect) {
|
407
619
|
intersectionRect = computeRectIntersection(parentRect, intersectionRect);
|
408
|
-
|
409
|
-
if (!intersectionRect) break;
|
410
620
|
}
|
411
|
-
|
621
|
+
if (!intersectionRect) break;
|
622
|
+
parent = parent && getParentNode(parent);
|
412
623
|
}
|
413
624
|
return intersectionRect;
|
414
625
|
};
|
@@ -416,17 +627,18 @@ IntersectionObserver.prototype._computeTargetAndRootIntersection =
|
|
416
627
|
|
417
628
|
/**
|
418
629
|
* Returns the root rect after being expanded by the rootMargin value.
|
419
|
-
* @return {
|
630
|
+
* @return {ClientRect} The expanded root rect.
|
420
631
|
* @private
|
421
632
|
*/
|
422
633
|
IntersectionObserver.prototype._getRootRect = function() {
|
423
634
|
var rootRect;
|
424
|
-
if (this.root) {
|
635
|
+
if (this.root && !isDoc(this.root)) {
|
425
636
|
rootRect = getBoundingClientRect(this.root);
|
426
637
|
} else {
|
427
638
|
// Use <html>/<body> instead of window since scroll bars affect size.
|
428
|
-
var
|
429
|
-
var
|
639
|
+
var doc = isDoc(this.root) ? this.root : document;
|
640
|
+
var html = doc.documentElement;
|
641
|
+
var body = doc.body;
|
430
642
|
rootRect = {
|
431
643
|
top: 0,
|
432
644
|
left: 0,
|
@@ -442,8 +654,8 @@ IntersectionObserver.prototype._getRootRect = function() {
|
|
442
654
|
|
443
655
|
/**
|
444
656
|
* Accepts a rect and expands it by the rootMargin value.
|
445
|
-
* @param {
|
446
|
-
* @return {
|
657
|
+
* @param {DOMRect|ClientRect} rect The rect object to expand.
|
658
|
+
* @return {ClientRect} The expanded rect.
|
447
659
|
* @private
|
448
660
|
*/
|
449
661
|
IntersectionObserver.prototype._expandRectByRootMargin = function(rect) {
|
@@ -517,7 +729,12 @@ IntersectionObserver.prototype._rootIsInDom = function() {
|
|
517
729
|
* @private
|
518
730
|
*/
|
519
731
|
IntersectionObserver.prototype._rootContainsTarget = function(target) {
|
520
|
-
|
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
|
+
);
|
521
738
|
};
|
522
739
|
|
523
740
|
|
@@ -604,8 +821,8 @@ function removeEvent(node, event, fn, opt_useCapture) {
|
|
604
821
|
if (typeof node.removeEventListener == 'function') {
|
605
822
|
node.removeEventListener(event, fn, opt_useCapture || false);
|
606
823
|
}
|
607
|
-
else if (typeof node.
|
608
|
-
node.
|
824
|
+
else if (typeof node.detachEvent == 'function') {
|
825
|
+
node.detachEvent('on' + event, fn);
|
609
826
|
}
|
610
827
|
}
|
611
828
|
|
@@ -614,8 +831,8 @@ function removeEvent(node, event, fn, opt_useCapture) {
|
|
614
831
|
* Returns the intersection between two rect objects.
|
615
832
|
* @param {Object} rect1 The first rect.
|
616
833
|
* @param {Object} rect2 The second rect.
|
617
|
-
* @return {?Object} The intersection rect or undefined if no
|
618
|
-
* is found.
|
834
|
+
* @return {?Object|?ClientRect} The intersection rect or undefined if no
|
835
|
+
* intersection is found.
|
619
836
|
*/
|
620
837
|
function computeRectIntersection(rect1, rect2) {
|
621
838
|
var top = Math.max(rect1.top, rect2.top);
|
@@ -632,14 +849,14 @@ function computeRectIntersection(rect1, rect2) {
|
|
632
849
|
right: right,
|
633
850
|
width: width,
|
634
851
|
height: height
|
635
|
-
};
|
852
|
+
} || null;
|
636
853
|
}
|
637
854
|
|
638
855
|
|
639
856
|
/**
|
640
857
|
* Shims the native getBoundingClientRect for compatibility with older IE.
|
641
858
|
* @param {Element} el The element whose bounding rect to get.
|
642
|
-
* @return {
|
859
|
+
* @return {DOMRect|ClientRect} The (possibly shimmed) rect of the element.
|
643
860
|
*/
|
644
861
|
function getBoundingClientRect(el) {
|
645
862
|
var rect;
|
@@ -671,7 +888,7 @@ function getBoundingClientRect(el) {
|
|
671
888
|
/**
|
672
889
|
* Returns an empty rect object. An empty rect is returned when an element
|
673
890
|
* is not in the DOM.
|
674
|
-
* @return {
|
891
|
+
* @return {ClientRect} The empty rect.
|
675
892
|
*/
|
676
893
|
function getEmptyRect() {
|
677
894
|
return {
|
@@ -684,6 +901,57 @@ function getEmptyRect() {
|
|
684
901
|
};
|
685
902
|
}
|
686
903
|
|
904
|
+
|
905
|
+
/**
|
906
|
+
* Ensure that the result has all of the necessary fields of the DOMRect.
|
907
|
+
* Specifically this ensures that `x` and `y` fields are set.
|
908
|
+
*
|
909
|
+
* @param {?DOMRect|?ClientRect} rect
|
910
|
+
* @return {?DOMRect}
|
911
|
+
*/
|
912
|
+
function ensureDOMRect(rect) {
|
913
|
+
// A `DOMRect` object has `x` and `y` fields.
|
914
|
+
if (!rect || 'x' in rect) {
|
915
|
+
return rect;
|
916
|
+
}
|
917
|
+
// A IE's `ClientRect` type does not have `x` and `y`. The same is the case
|
918
|
+
// for internally calculated Rect objects. For the purposes of
|
919
|
+
// `IntersectionObserver`, it's sufficient to simply mirror `left` and `top`
|
920
|
+
// for these fields.
|
921
|
+
return {
|
922
|
+
top: rect.top,
|
923
|
+
y: rect.top,
|
924
|
+
bottom: rect.bottom,
|
925
|
+
left: rect.left,
|
926
|
+
x: rect.left,
|
927
|
+
right: rect.right,
|
928
|
+
width: rect.width,
|
929
|
+
height: rect.height
|
930
|
+
};
|
931
|
+
}
|
932
|
+
|
933
|
+
|
934
|
+
/**
|
935
|
+
* Inverts the intersection and bounding rect from the parent (frame) BCR to
|
936
|
+
* the local BCR space.
|
937
|
+
* @param {DOMRect|ClientRect} parentBoundingRect The parent's bound client rect.
|
938
|
+
* @param {DOMRect|ClientRect} parentIntersectionRect The parent's own intersection rect.
|
939
|
+
* @return {ClientRect} The local root bounding rect for the parent's children.
|
940
|
+
*/
|
941
|
+
function convertFromParentRect(parentBoundingRect, parentIntersectionRect) {
|
942
|
+
var top = parentIntersectionRect.top - parentBoundingRect.top;
|
943
|
+
var left = parentIntersectionRect.left - parentBoundingRect.left;
|
944
|
+
return {
|
945
|
+
top: top,
|
946
|
+
left: left,
|
947
|
+
height: parentIntersectionRect.height,
|
948
|
+
width: parentIntersectionRect.width,
|
949
|
+
bottom: top + parentIntersectionRect.height,
|
950
|
+
right: left + parentIntersectionRect.width
|
951
|
+
};
|
952
|
+
}
|
953
|
+
|
954
|
+
|
687
955
|
/**
|
688
956
|
* Checks to see if a parent element contains a child element (including inside
|
689
957
|
* shadow DOM).
|
@@ -711,16 +979,36 @@ function containsDeep(parent, child) {
|
|
711
979
|
function getParentNode(node) {
|
712
980
|
var parent = node.parentNode;
|
713
981
|
|
982
|
+
if (node.nodeType == /* DOCUMENT */ 9 && node != document) {
|
983
|
+
// If this node is a document node, look for the embedding frame.
|
984
|
+
return getFrameElement(node);
|
985
|
+
}
|
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
|
+
|
714
992
|
if (parent && parent.nodeType == 11 && parent.host) {
|
715
993
|
// If the parent is a shadow root, return the host element.
|
716
994
|
return parent.host;
|
717
995
|
}
|
996
|
+
|
718
997
|
return parent;
|
719
998
|
}
|
720
999
|
|
1000
|
+
/**
|
1001
|
+
* Returns true if `node` is a Document.
|
1002
|
+
* @param {!Node} node
|
1003
|
+
* @returns {boolean}
|
1004
|
+
*/
|
1005
|
+
function isDoc(node) {
|
1006
|
+
return node && node.nodeType === 9;
|
1007
|
+
}
|
1008
|
+
|
721
1009
|
|
722
1010
|
// Exposes the constructors globally.
|
723
1011
|
window.IntersectionObserver = IntersectionObserver;
|
724
1012
|
window.IntersectionObserverEntry = IntersectionObserverEntry;
|
725
1013
|
|
726
|
-
}(
|
1014
|
+
}());
|