intersection-observer 0.6.0 → 0.10.0
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 +64 -5
- package/intersection-observer-test.html +7 -0
- package/intersection-observer-test.js +1427 -3
- package/intersection-observer.js +317 -68
- package/package.json +1 -1
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 &&
|
@@ -31,6 +34,12 @@ if ('IntersectionObserver' in window &&
|
|
31
34
|
}
|
32
35
|
|
33
36
|
|
37
|
+
/**
|
38
|
+
* A local reference to the document.
|
39
|
+
*/
|
40
|
+
var document = window.document;
|
41
|
+
|
42
|
+
|
34
43
|
/**
|
35
44
|
* An IntersectionObserver registry. This registry exists to hold a strong
|
36
45
|
* reference to IntersectionObserver instances currently observing a target
|
@@ -39,6 +48,19 @@ if ('IntersectionObserver' in window &&
|
|
39
48
|
*/
|
40
49
|
var registry = [];
|
41
50
|
|
51
|
+
/**
|
52
|
+
* The signal updater for cross-origin intersection. When not null, it means
|
53
|
+
* that the polyfill is configured to work in a cross-origin mode.
|
54
|
+
* @type {function(DOMRect|ClientRect, DOMRect|ClientRect)}
|
55
|
+
*/
|
56
|
+
var crossOriginUpdater = null;
|
57
|
+
|
58
|
+
/**
|
59
|
+
* The current cross-origin intersection. Only used in the cross-origin mode.
|
60
|
+
* @type {DOMRect|ClientRect}
|
61
|
+
*/
|
62
|
+
var crossOriginRect = null;
|
63
|
+
|
42
64
|
|
43
65
|
/**
|
44
66
|
* Creates the global IntersectionObserverEntry constructor.
|
@@ -49,9 +71,9 @@ var registry = [];
|
|
49
71
|
function IntersectionObserverEntry(entry) {
|
50
72
|
this.time = entry.time;
|
51
73
|
this.target = entry.target;
|
52
|
-
this.rootBounds = entry.rootBounds;
|
53
|
-
this.boundingClientRect = entry.boundingClientRect;
|
54
|
-
this.intersectionRect = entry.intersectionRect || getEmptyRect();
|
74
|
+
this.rootBounds = ensureDOMRect(entry.rootBounds);
|
75
|
+
this.boundingClientRect = ensureDOMRect(entry.boundingClientRect);
|
76
|
+
this.intersectionRect = ensureDOMRect(entry.intersectionRect || getEmptyRect());
|
55
77
|
this.isIntersecting = !!entry.intersectionRect;
|
56
78
|
|
57
79
|
// Calculates the intersection ratio.
|
@@ -109,6 +131,11 @@ function IntersectionObserver(callback, opt_options) {
|
|
109
131
|
this.rootMargin = this._rootMarginValues.map(function(margin) {
|
110
132
|
return margin.value + margin.unit;
|
111
133
|
}).join(' ');
|
134
|
+
|
135
|
+
/** @private @const {!Array<!Document>} */
|
136
|
+
this._monitoringDocuments = [];
|
137
|
+
/** @private @const {!Array<function()>} */
|
138
|
+
this._monitoringUnsubscribes = [];
|
112
139
|
}
|
113
140
|
|
114
141
|
|
@@ -133,6 +160,45 @@ IntersectionObserver.prototype.POLL_INTERVAL = null;
|
|
133
160
|
IntersectionObserver.prototype.USE_MUTATION_OBSERVER = true;
|
134
161
|
|
135
162
|
|
163
|
+
/**
|
164
|
+
* Sets up the polyfill in the cross-origin mode. The result is the
|
165
|
+
* updater function that accepts two arguments: `boundingClientRect` and
|
166
|
+
* `intersectionRect` - just as these fields would be available to the
|
167
|
+
* parent via `IntersectionObserverEntry`. This function should be called
|
168
|
+
* each time the iframe receives intersection information from the parent
|
169
|
+
* window, e.g. via messaging.
|
170
|
+
* @return {function(DOMRect|ClientRect, DOMRect|ClientRect)}
|
171
|
+
*/
|
172
|
+
IntersectionObserver._setupCrossOriginUpdater = function() {
|
173
|
+
if (!crossOriginUpdater) {
|
174
|
+
/**
|
175
|
+
* @param {DOMRect|ClientRect} boundingClientRect
|
176
|
+
* @param {DOMRect|ClientRect} intersectionRect
|
177
|
+
*/
|
178
|
+
crossOriginUpdater = function(boundingClientRect, intersectionRect) {
|
179
|
+
if (!boundingClientRect || !intersectionRect) {
|
180
|
+
crossOriginRect = getEmptyRect();
|
181
|
+
} else {
|
182
|
+
crossOriginRect = convertFromParentRect(boundingClientRect, intersectionRect);
|
183
|
+
}
|
184
|
+
registry.forEach(function(observer) {
|
185
|
+
observer._checkForIntersections();
|
186
|
+
});
|
187
|
+
};
|
188
|
+
}
|
189
|
+
return crossOriginUpdater;
|
190
|
+
};
|
191
|
+
|
192
|
+
|
193
|
+
/**
|
194
|
+
* Resets the cross-origin mode.
|
195
|
+
*/
|
196
|
+
IntersectionObserver._resetCrossOriginUpdater = function() {
|
197
|
+
crossOriginUpdater = null;
|
198
|
+
crossOriginRect = null;
|
199
|
+
};
|
200
|
+
|
201
|
+
|
136
202
|
/**
|
137
203
|
* Starts observing a target element for intersection changes based on
|
138
204
|
* the thresholds values.
|
@@ -153,7 +219,7 @@ IntersectionObserver.prototype.observe = function(target) {
|
|
153
219
|
|
154
220
|
this._registerInstance();
|
155
221
|
this._observationTargets.push({element: target, entry: null});
|
156
|
-
this._monitorIntersections();
|
222
|
+
this._monitorIntersections(target.ownerDocument);
|
157
223
|
this._checkForIntersections();
|
158
224
|
};
|
159
225
|
|
@@ -165,11 +231,10 @@ IntersectionObserver.prototype.observe = function(target) {
|
|
165
231
|
IntersectionObserver.prototype.unobserve = function(target) {
|
166
232
|
this._observationTargets =
|
167
233
|
this._observationTargets.filter(function(item) {
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
if (
|
172
|
-
this._unmonitorIntersections();
|
234
|
+
return item.element != target;
|
235
|
+
});
|
236
|
+
this._unmonitorIntersections(target.ownerDocument);
|
237
|
+
if (this._observationTargets.length == 0) {
|
173
238
|
this._unregisterInstance();
|
174
239
|
}
|
175
240
|
};
|
@@ -180,7 +245,7 @@ IntersectionObserver.prototype.unobserve = function(target) {
|
|
180
245
|
*/
|
181
246
|
IntersectionObserver.prototype.disconnect = function() {
|
182
247
|
this._observationTargets = [];
|
183
|
-
this.
|
248
|
+
this._unmonitorAllIntersections();
|
184
249
|
this._unregisterInstance();
|
185
250
|
};
|
186
251
|
|
@@ -253,31 +318,67 @@ IntersectionObserver.prototype._parseRootMargin = function(opt_rootMargin) {
|
|
253
318
|
/**
|
254
319
|
* Starts polling for intersection changes if the polling is not already
|
255
320
|
* happening, and if the page's visibility state is visible.
|
321
|
+
* @param {!Document} doc
|
256
322
|
* @private
|
257
323
|
*/
|
258
|
-
IntersectionObserver.prototype._monitorIntersections = function() {
|
259
|
-
|
260
|
-
|
324
|
+
IntersectionObserver.prototype._monitorIntersections = function(doc) {
|
325
|
+
var win = doc.defaultView;
|
326
|
+
if (!win) {
|
327
|
+
// Already destroyed.
|
328
|
+
return;
|
329
|
+
}
|
330
|
+
if (this._monitoringDocuments.indexOf(doc) != -1) {
|
331
|
+
// Already monitoring.
|
332
|
+
return;
|
333
|
+
}
|
261
334
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
335
|
+
// Private state for monitoring.
|
336
|
+
var callback = this._checkForIntersections;
|
337
|
+
var monitoringInterval = null;
|
338
|
+
var domObserver = null;
|
339
|
+
|
340
|
+
// If a poll interval is set, use polling instead of listening to
|
341
|
+
// resize and scroll events or DOM mutations.
|
342
|
+
if (this.POLL_INTERVAL) {
|
343
|
+
monitoringInterval = win.setInterval(callback, this.POLL_INTERVAL);
|
344
|
+
} else {
|
345
|
+
addEvent(win, 'resize', callback, true);
|
346
|
+
addEvent(doc, 'scroll', callback, true);
|
347
|
+
if (this.USE_MUTATION_OBSERVER && 'MutationObserver' in win) {
|
348
|
+
domObserver = new win.MutationObserver(callback);
|
349
|
+
domObserver.observe(doc, {
|
350
|
+
attributes: true,
|
351
|
+
childList: true,
|
352
|
+
characterData: true,
|
353
|
+
subtree: true
|
354
|
+
});
|
267
355
|
}
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
});
|
356
|
+
}
|
357
|
+
|
358
|
+
this._monitoringDocuments.push(doc);
|
359
|
+
this._monitoringUnsubscribes.push(function() {
|
360
|
+
// Get the window object again. When a friendly iframe is destroyed, it
|
361
|
+
// will be null.
|
362
|
+
var win = doc.defaultView;
|
363
|
+
|
364
|
+
if (win) {
|
365
|
+
if (monitoringInterval) {
|
366
|
+
win.clearInterval(monitoringInterval);
|
280
367
|
}
|
368
|
+
removeEvent(win, 'resize', callback, true);
|
369
|
+
}
|
370
|
+
|
371
|
+
removeEvent(doc, 'scroll', callback, true);
|
372
|
+
if (domObserver) {
|
373
|
+
domObserver.disconnect();
|
374
|
+
}
|
375
|
+
});
|
376
|
+
|
377
|
+
// Also monitor the parent.
|
378
|
+
if (doc != (this.root && this.root.ownerDocument || document)) {
|
379
|
+
var frame = getFrameElement(doc);
|
380
|
+
if (frame) {
|
381
|
+
this._monitorIntersections(frame.ownerDocument);
|
281
382
|
}
|
282
383
|
}
|
283
384
|
};
|
@@ -285,26 +386,70 @@ IntersectionObserver.prototype._monitorIntersections = function() {
|
|
285
386
|
|
286
387
|
/**
|
287
388
|
* Stops polling for intersection changes.
|
389
|
+
* @param {!Document} doc
|
288
390
|
* @private
|
289
391
|
*/
|
290
|
-
IntersectionObserver.prototype._unmonitorIntersections = function() {
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
this._monitoringInterval = null;
|
392
|
+
IntersectionObserver.prototype._unmonitorIntersections = function(doc) {
|
393
|
+
var index = this._monitoringDocuments.indexOf(doc);
|
394
|
+
if (index == -1) {
|
395
|
+
return;
|
396
|
+
}
|
296
397
|
|
297
|
-
|
298
|
-
|
398
|
+
var rootDoc = (this.root && this.root.ownerDocument || document);
|
399
|
+
|
400
|
+
// Check if any dependent targets are still remaining.
|
401
|
+
var hasDependentTargets =
|
402
|
+
this._observationTargets.some(function(item) {
|
403
|
+
var itemDoc = item.element.ownerDocument;
|
404
|
+
// Target is in this context.
|
405
|
+
if (itemDoc == doc) {
|
406
|
+
return true;
|
407
|
+
}
|
408
|
+
// Target is nested in this context.
|
409
|
+
while (itemDoc && itemDoc != rootDoc) {
|
410
|
+
var frame = getFrameElement(itemDoc);
|
411
|
+
itemDoc = frame && frame.ownerDocument;
|
412
|
+
if (itemDoc == doc) {
|
413
|
+
return true;
|
414
|
+
}
|
415
|
+
}
|
416
|
+
return false;
|
417
|
+
});
|
418
|
+
if (hasDependentTargets) {
|
419
|
+
return;
|
420
|
+
}
|
299
421
|
|
300
|
-
|
301
|
-
|
302
|
-
|
422
|
+
// Unsubscribe.
|
423
|
+
var unsubscribe = this._monitoringUnsubscribes[index];
|
424
|
+
this._monitoringDocuments.splice(index, 1);
|
425
|
+
this._monitoringUnsubscribes.splice(index, 1);
|
426
|
+
unsubscribe();
|
427
|
+
|
428
|
+
// Also unmonitor the parent.
|
429
|
+
if (doc != rootDoc) {
|
430
|
+
var frame = getFrameElement(doc);
|
431
|
+
if (frame) {
|
432
|
+
this._unmonitorIntersections(frame.ownerDocument);
|
303
433
|
}
|
304
434
|
}
|
305
435
|
};
|
306
436
|
|
307
437
|
|
438
|
+
/**
|
439
|
+
* Stops polling for intersection changes.
|
440
|
+
* @param {!Document} doc
|
441
|
+
* @private
|
442
|
+
*/
|
443
|
+
IntersectionObserver.prototype._unmonitorAllIntersections = function() {
|
444
|
+
var unsubscribes = this._monitoringUnsubscribes.slice(0);
|
445
|
+
this._monitoringDocuments.length = 0;
|
446
|
+
this._monitoringUnsubscribes.length = 0;
|
447
|
+
for (var i = 0; i < unsubscribes.length; i++) {
|
448
|
+
unsubscribes[i]();
|
449
|
+
}
|
450
|
+
};
|
451
|
+
|
452
|
+
|
308
453
|
/**
|
309
454
|
* Scans each observation target for intersection changes and adds them
|
310
455
|
* to the internal entries queue. If new entries are found, it
|
@@ -312,6 +457,11 @@ IntersectionObserver.prototype._unmonitorIntersections = function() {
|
|
312
457
|
* @private
|
313
458
|
*/
|
314
459
|
IntersectionObserver.prototype._checkForIntersections = function() {
|
460
|
+
if (!this.root && crossOriginUpdater && !crossOriginRect) {
|
461
|
+
// Cross origin monitoring, but no initial data available yet.
|
462
|
+
return;
|
463
|
+
}
|
464
|
+
|
315
465
|
var rootIsInDom = this._rootIsInDom();
|
316
466
|
var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect();
|
317
467
|
|
@@ -321,13 +471,13 @@ IntersectionObserver.prototype._checkForIntersections = function() {
|
|
321
471
|
var rootContainsTarget = this._rootContainsTarget(target);
|
322
472
|
var oldEntry = item.entry;
|
323
473
|
var intersectionRect = rootIsInDom && rootContainsTarget &&
|
324
|
-
this._computeTargetAndRootIntersection(target, rootRect);
|
474
|
+
this._computeTargetAndRootIntersection(target, targetRect, rootRect);
|
325
475
|
|
326
476
|
var newEntry = item.entry = new IntersectionObserverEntry({
|
327
477
|
time: now(),
|
328
478
|
target: target,
|
329
479
|
boundingClientRect: targetRect,
|
330
|
-
rootBounds: rootRect,
|
480
|
+
rootBounds: crossOriginUpdater && !this.root ? null : rootRect,
|
331
481
|
intersectionRect: intersectionRect
|
332
482
|
});
|
333
483
|
|
@@ -361,6 +511,7 @@ IntersectionObserver.prototype._checkForIntersections = function() {
|
|
361
511
|
* TODO(philipwalton): at this time clip-path is not considered.
|
362
512
|
* https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo
|
363
513
|
* @param {Element} target The target DOM element
|
514
|
+
* @param {Object} targetRect The bounding rect of the target.
|
364
515
|
* @param {Object} rootRect The bounding rect of the root after being
|
365
516
|
* expanded by the rootMargin value.
|
366
517
|
* @return {?Object} The final intersection rect object or undefined if no
|
@@ -368,34 +519,61 @@ IntersectionObserver.prototype._checkForIntersections = function() {
|
|
368
519
|
* @private
|
369
520
|
*/
|
370
521
|
IntersectionObserver.prototype._computeTargetAndRootIntersection =
|
371
|
-
function(target, rootRect) {
|
372
|
-
|
522
|
+
function(target, targetRect, rootRect) {
|
373
523
|
// If the element isn't displayed, an intersection can't happen.
|
374
524
|
if (window.getComputedStyle(target).display == 'none') return;
|
375
525
|
|
376
|
-
var targetRect = getBoundingClientRect(target);
|
377
526
|
var intersectionRect = targetRect;
|
378
527
|
var parent = getParentNode(target);
|
379
528
|
var atRoot = false;
|
380
529
|
|
381
|
-
while (!atRoot) {
|
530
|
+
while (!atRoot && parent) {
|
382
531
|
var parentRect = null;
|
383
532
|
var parentComputedStyle = parent.nodeType == 1 ?
|
384
533
|
window.getComputedStyle(parent) : {};
|
385
534
|
|
386
535
|
// If the parent isn't displayed, an intersection can't happen.
|
387
|
-
if (parentComputedStyle.display == 'none') return;
|
536
|
+
if (parentComputedStyle.display == 'none') return null;
|
388
537
|
|
389
|
-
if (parent == this.root || parent ==
|
538
|
+
if (parent == this.root || parent.nodeType == /* DOCUMENT */ 9) {
|
390
539
|
atRoot = true;
|
391
|
-
|
540
|
+
if (parent == this.root || parent == document) {
|
541
|
+
if (crossOriginUpdater && !this.root) {
|
542
|
+
if (!crossOriginRect ||
|
543
|
+
crossOriginRect.width == 0 && crossOriginRect.height == 0) {
|
544
|
+
// A 0-size cross-origin intersection means no-intersection.
|
545
|
+
parent = null;
|
546
|
+
parentRect = null;
|
547
|
+
intersectionRect = null;
|
548
|
+
} else {
|
549
|
+
parentRect = crossOriginRect;
|
550
|
+
}
|
551
|
+
} else {
|
552
|
+
parentRect = rootRect;
|
553
|
+
}
|
554
|
+
} else {
|
555
|
+
// Check if there's a frame that can be navigated to.
|
556
|
+
var frame = getParentNode(parent);
|
557
|
+
var frameRect = frame && getBoundingClientRect(frame);
|
558
|
+
var frameIntersect =
|
559
|
+
frame &&
|
560
|
+
this._computeTargetAndRootIntersection(frame, frameRect, rootRect);
|
561
|
+
if (frameRect && frameIntersect) {
|
562
|
+
parent = frame;
|
563
|
+
parentRect = convertFromParentRect(frameRect, frameIntersect);
|
564
|
+
} else {
|
565
|
+
parent = null;
|
566
|
+
intersectionRect = null;
|
567
|
+
}
|
568
|
+
}
|
392
569
|
} else {
|
393
570
|
// If the element has a non-visible overflow, and it's not the <body>
|
394
571
|
// or <html> element, update the intersection rect.
|
395
572
|
// Note: <body> and <html> cannot be clipped to a rect that's not also
|
396
573
|
// the document rect, so no need to compute a new intersection.
|
397
|
-
|
398
|
-
|
574
|
+
var doc = parent.ownerDocument;
|
575
|
+
if (parent != doc.body &&
|
576
|
+
parent != doc.documentElement &&
|
399
577
|
parentComputedStyle.overflow != 'visible') {
|
400
578
|
parentRect = getBoundingClientRect(parent);
|
401
579
|
}
|
@@ -405,10 +583,9 @@ IntersectionObserver.prototype._computeTargetAndRootIntersection =
|
|
405
583
|
// calculate new intersection data.
|
406
584
|
if (parentRect) {
|
407
585
|
intersectionRect = computeRectIntersection(parentRect, intersectionRect);
|
408
|
-
|
409
|
-
if (!intersectionRect) break;
|
410
586
|
}
|
411
|
-
|
587
|
+
if (!intersectionRect) break;
|
588
|
+
parent = parent && getParentNode(parent);
|
412
589
|
}
|
413
590
|
return intersectionRect;
|
414
591
|
};
|
@@ -416,7 +593,7 @@ IntersectionObserver.prototype._computeTargetAndRootIntersection =
|
|
416
593
|
|
417
594
|
/**
|
418
595
|
* Returns the root rect after being expanded by the rootMargin value.
|
419
|
-
* @return {
|
596
|
+
* @return {ClientRect} The expanded root rect.
|
420
597
|
* @private
|
421
598
|
*/
|
422
599
|
IntersectionObserver.prototype._getRootRect = function() {
|
@@ -442,8 +619,8 @@ IntersectionObserver.prototype._getRootRect = function() {
|
|
442
619
|
|
443
620
|
/**
|
444
621
|
* Accepts a rect and expands it by the rootMargin value.
|
445
|
-
* @param {
|
446
|
-
* @return {
|
622
|
+
* @param {DOMRect|ClientRect} rect The rect object to expand.
|
623
|
+
* @return {ClientRect} The expanded rect.
|
447
624
|
* @private
|
448
625
|
*/
|
449
626
|
IntersectionObserver.prototype._expandRectByRootMargin = function(rect) {
|
@@ -517,7 +694,8 @@ IntersectionObserver.prototype._rootIsInDom = function() {
|
|
517
694
|
* @private
|
518
695
|
*/
|
519
696
|
IntersectionObserver.prototype._rootContainsTarget = function(target) {
|
520
|
-
return containsDeep(this.root || document, target)
|
697
|
+
return containsDeep(this.root || document, target) &&
|
698
|
+
(!this.root || this.root.ownerDocument == target.ownerDocument);
|
521
699
|
};
|
522
700
|
|
523
701
|
|
@@ -614,8 +792,8 @@ function removeEvent(node, event, fn, opt_useCapture) {
|
|
614
792
|
* Returns the intersection between two rect objects.
|
615
793
|
* @param {Object} rect1 The first rect.
|
616
794
|
* @param {Object} rect2 The second rect.
|
617
|
-
* @return {?Object} The intersection rect or undefined if no
|
618
|
-
* is found.
|
795
|
+
* @return {?Object|?ClientRect} The intersection rect or undefined if no
|
796
|
+
* intersection is found.
|
619
797
|
*/
|
620
798
|
function computeRectIntersection(rect1, rect2) {
|
621
799
|
var top = Math.max(rect1.top, rect2.top);
|
@@ -632,14 +810,14 @@ function computeRectIntersection(rect1, rect2) {
|
|
632
810
|
right: right,
|
633
811
|
width: width,
|
634
812
|
height: height
|
635
|
-
};
|
813
|
+
} || null;
|
636
814
|
}
|
637
815
|
|
638
816
|
|
639
817
|
/**
|
640
818
|
* Shims the native getBoundingClientRect for compatibility with older IE.
|
641
819
|
* @param {Element} el The element whose bounding rect to get.
|
642
|
-
* @return {
|
820
|
+
* @return {DOMRect|ClientRect} The (possibly shimmed) rect of the element.
|
643
821
|
*/
|
644
822
|
function getBoundingClientRect(el) {
|
645
823
|
var rect;
|
@@ -671,7 +849,7 @@ function getBoundingClientRect(el) {
|
|
671
849
|
/**
|
672
850
|
* Returns an empty rect object. An empty rect is returned when an element
|
673
851
|
* is not in the DOM.
|
674
|
-
* @return {
|
852
|
+
* @return {ClientRect} The empty rect.
|
675
853
|
*/
|
676
854
|
function getEmptyRect() {
|
677
855
|
return {
|
@@ -684,6 +862,57 @@ function getEmptyRect() {
|
|
684
862
|
};
|
685
863
|
}
|
686
864
|
|
865
|
+
|
866
|
+
/**
|
867
|
+
* Ensure that the result has all of the necessary fields of the DOMRect.
|
868
|
+
* Specifically this ensures that `x` and `y` fields are set.
|
869
|
+
*
|
870
|
+
* @param {?DOMRect|?ClientRect} rect
|
871
|
+
* @return {?DOMRect}
|
872
|
+
*/
|
873
|
+
function ensureDOMRect(rect) {
|
874
|
+
// A `DOMRect` object has `x` and `y` fields.
|
875
|
+
if (!rect || 'x' in rect) {
|
876
|
+
return rect;
|
877
|
+
}
|
878
|
+
// A IE's `ClientRect` type does not have `x` and `y`. The same is the case
|
879
|
+
// for internally calculated Rect objects. For the purposes of
|
880
|
+
// `IntersectionObserver`, it's sufficient to simply mirror `left` and `top`
|
881
|
+
// for these fields.
|
882
|
+
return {
|
883
|
+
top: rect.top,
|
884
|
+
y: rect.top,
|
885
|
+
bottom: rect.bottom,
|
886
|
+
left: rect.left,
|
887
|
+
x: rect.left,
|
888
|
+
right: rect.right,
|
889
|
+
width: rect.width,
|
890
|
+
height: rect.height
|
891
|
+
};
|
892
|
+
}
|
893
|
+
|
894
|
+
|
895
|
+
/**
|
896
|
+
* Inverts the intersection and bounding rect from the parent (frame) BCR to
|
897
|
+
* the local BCR space.
|
898
|
+
* @param {DOMRect|ClientRect} parentBoundingRect The parent's bound client rect.
|
899
|
+
* @param {DOMRect|ClientRect} parentIntersectionRect The parent's own intersection rect.
|
900
|
+
* @return {ClientRect} The local root bounding rect for the parent's children.
|
901
|
+
*/
|
902
|
+
function convertFromParentRect(parentBoundingRect, parentIntersectionRect) {
|
903
|
+
var top = parentIntersectionRect.top - parentBoundingRect.top;
|
904
|
+
var left = parentIntersectionRect.left - parentBoundingRect.left;
|
905
|
+
return {
|
906
|
+
top: top,
|
907
|
+
left: left,
|
908
|
+
height: parentIntersectionRect.height,
|
909
|
+
width: parentIntersectionRect.width,
|
910
|
+
bottom: top + parentIntersectionRect.height,
|
911
|
+
right: left + parentIntersectionRect.width
|
912
|
+
};
|
913
|
+
}
|
914
|
+
|
915
|
+
|
687
916
|
/**
|
688
917
|
* Checks to see if a parent element contains a child element (including inside
|
689
918
|
* shadow DOM).
|
@@ -711,6 +940,11 @@ function containsDeep(parent, child) {
|
|
711
940
|
function getParentNode(node) {
|
712
941
|
var parent = node.parentNode;
|
713
942
|
|
943
|
+
if (node.nodeType == /* DOCUMENT */ 9 && node != document) {
|
944
|
+
// If this node is a document node, look for the embedding frame.
|
945
|
+
return getFrameElement(node);
|
946
|
+
}
|
947
|
+
|
714
948
|
if (parent && parent.nodeType == 11 && parent.host) {
|
715
949
|
// If the parent is a shadow root, return the host element.
|
716
950
|
return parent.host;
|
@@ -725,8 +959,23 @@ function getParentNode(node) {
|
|
725
959
|
}
|
726
960
|
|
727
961
|
|
962
|
+
/**
|
963
|
+
* Returns the embedding frame element, if any.
|
964
|
+
* @param {!Document} doc
|
965
|
+
* @return {!Element}
|
966
|
+
*/
|
967
|
+
function getFrameElement(doc) {
|
968
|
+
try {
|
969
|
+
return doc.defaultView && doc.defaultView.frameElement || null;
|
970
|
+
} catch (e) {
|
971
|
+
// Ignore the error.
|
972
|
+
return null;
|
973
|
+
}
|
974
|
+
}
|
975
|
+
|
976
|
+
|
728
977
|
// Exposes the constructors globally.
|
729
978
|
window.IntersectionObserver = IntersectionObserver;
|
730
979
|
window.IntersectionObserverEntry = IntersectionObserverEntry;
|
731
980
|
|
732
|
-
}(
|
981
|
+
}());
|