intersection-observer 0.6.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}());
|