-
Notifications
You must be signed in to change notification settings - Fork 1
/
offsetParent-polyfill.js
129 lines (110 loc) · 3.96 KB
/
offsetParent-polyfill.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
(() => {
const originalOffsetParent = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetParent').get;
const originalOffsetTop = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetTop').get;
const originalOffsetLeft = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetLeft').get;
function flatTreeParent(element) {
if (element.assignedSlot) {
return element.assignedSlot;
}
if (element.parentNode instanceof ShadowRoot) {
return element.parentNode.host;
}
return element.parentNode;
}
function ancestorTreeScopes(element) {
const scopes = new Set();
let currentScope = element.getRootNode();
while (currentScope) {
scopes.add(currentScope);
currentScope = currentScope.parentNode
? currentScope.parentNode.getRootNode()
: null;
}
return scopes;
}
function offsetParentPolyfill(element, isNewBehavior) {
// Do an initial walk to check for display:none ancestors.
for (let ancestor = element; ancestor; ancestor = flatTreeParent(ancestor)) {
if (!(ancestor instanceof Element))
continue;
if (getComputedStyle(ancestor).display === 'none')
return null;
}
let scopes = null;
if (isNewBehavior)
scopes = ancestorTreeScopes(element);
for (let ancestor = flatTreeParent(element); ancestor; ancestor = flatTreeParent(ancestor)) {
if (!(ancestor instanceof Element))
continue;
const style = getComputedStyle(ancestor);
// display:contents nodes aren't in the layout tree so they should be skipped.
if (style.display === 'contents')
continue;
if (style.position !== 'static' || style.filter !== 'none') {
if (isNewBehavior) {
if (scopes.has(ancestor.getRootNode())) {
return ancestor;
}
} else {
return ancestor;
}
}
if (ancestor.tagName === 'BODY')
return ancestor;
}
return null;
}
let isOffsetParentPatchedCached = null;
function isOffsetParentPatched() {
if (isOffsetParentPatchedCached !== null) {
return isOffsetParentPatchedCached;
}
const container = document.createElement('div');
container.style.position = 'absolute';
const shadowroot = container.attachShadow({mode: 'open'});
document.body.appendChild(container);
const lightChild = document.createElement('div');
container.appendChild(lightChild);
const shadowChild = document.createElement('div');
shadowChild.style.position = 'absolute';
shadowChild.appendChild(document.createElement('slot'));
shadowroot.appendChild(shadowChild);
const originalValue = originalOffsetParent.apply(lightChild);
if (originalValue == container) {
isOffsetParentPatchedCached = true;
} else if (originalValue == shadowChild) {
isOffsetParentPatchedCached = false;
} else {
console.error('what ', originalValue);
}
container.remove();
return isOffsetParentPatchedCached;
}
function offsetTopLeftPolyfill(element, originalFn) {
if (!isOffsetParentPatched())
return originalFn.apply(element);
let value = originalFn.apply(element);
let nextOffsetParent = offsetParentPolyfill(element, /*isNewBehavior=*/false);
const scopes = ancestorTreeScopes(element);
while (!scopes.has(nextOffsetParent.getRootNode())) {
value -= originalFn.apply(nextOffsetParent);
nextOffsetParent = offsetParentPolyfill(nextOffsetParent, /*isNewBehavior=*/false);
}
return value;
}
Object.defineProperty(HTMLElement.prototype, 'offsetParent', {
get() {
return offsetParentPolyfill(this, /*isNewBehavior=*/false);
}
});
Object.defineProperty(HTMLElement.prototype, 'offsetTop', {
get() {
return offsetTopLeftPolyfill(this, originalOffsetTop);
}
});
Object.defineProperty(HTMLElement.prototype, 'offsetLeft', {
get() {
return offsetTopLeftPolyfill(this, originalOffsetLeft);
}
});
})();