-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
141 lines (124 loc) · 4.77 KB
/
index.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
130
131
132
133
134
135
136
137
138
139
140
141
/**
* @module DomMeasurements
* @description Measure dom like getBoundingClientRect but ignore css transforms.
*/
/**
* Get offset added by borders of an element (from computedStyle)
* @param {HTMLElement} element The element to measure
* @returns {{top: Number, left: Number}} the border offset dimensions
*/
function getBordersOffset(element) {
const computedStyle = window.getComputedStyle(element);
return {
top: parseFloat(computedStyle.getPropertyValue('border-top-width'), 10),
left: parseFloat(computedStyle.getPropertyValue('border-left-width'), 10)
};
}
/**
* Does element has overflow (from computedStyle)?
* @param {HTMLElement} element The element to measure
* @return {Boolean}
*/
const hasOverflow = element =>
window.getComputedStyle(element).getPropertyValue('overflow') === 'visible';
/**
* Get filtered children of an element
* @param {HTMLElement} element The element to measure
* @param {String[]} [tagNames]
*/
const getChildren = (element, tagNames) =>
Array.from(element.children).filter(child =>
tagNames ? tagNames.includes(child.tagName.toLowerCase()) : child
);
export function getElementRect(element, offsetParent) {
let top = element.offsetTop;
let left = element.offsetLeft;
const width = element.offsetWidth;
const height = element.offsetHeight;
while (element.offsetParent) {
element = element.offsetParent;
const border = getBordersOffset(element);
top += border.top;
left += border.left;
if (offsetParent && element === offsetParent) {
break;
}
top += element.offsetTop;
left += element.offsetLeft;
}
return {
top,
left,
width,
height,
bottom: top + height,
right: left + width
};
}
export function getBoundingRect(element, offsetParent, scrollContainer = window) {
const elementRect = getElementRect(element, offsetParent);
if (scrollContainer) {
const scrollY = scrollContainer.scrollY || scrollContainer.scrollTop || 0;
const scrollX = scrollContainer.scrollX || scrollContainer.scrollLeft || 0;
elementRect.top -= scrollY;
elementRect.bottom -= scrollY;
elementRect.left -= scrollX;
elementRect.right -= scrollX;
}
return elementRect;
}
/**
* Recursively get the accumulated bounds of a group of elements and their children
* @param {HTMLElement[]} elements The elements to measure
* @param {HTMLElement} [offsetParent] Optional topmost offset parent to calculate position against, if passed an element which is not an offset parent or not a parent of element will be ignored.
* @param {string[]} [childTags] Optional element tags to filter by (for example, if you have components that their known root is always a 'div', you can save some recursion loops)
* @param {DomDimensions} [contentRect] The accumulated bounds (For recursion)
* @returns {DomDimensions}
*/
function getContentRectRecursive(elements, offsetParent, childTags, contentRect = {top: 0, left: 0, bottom: 0, right: 0}) {
for (const element of elements) {
const rect = getElementRect(element, offsetParent);
// If child has no size, meaning it is hidden, don't calculate it
if (rect.width > 0 && rect.height > 0) {
if (rect.left < contentRect.left) {
contentRect.left = rect.left;
}
if (rect.right > contentRect.right) {
contentRect.right = rect.right;
}
if (rect.top < contentRect.top) {
contentRect.top = rect.top;
}
if (rect.bottom > contentRect.bottom) {
contentRect.bottom = rect.bottom;
}
}
const elementChildren = getChildren(element, childTags);
// if a child has children and it's overflow value is not 'hidden', calculate their sizes too
if (elementChildren.length && hasOverflow(element)) {
getContentRectRecursive(elementChildren, offsetParent, childTags, contentRect);
}
}
contentRect.width = contentRect.right - contentRect.left;
contentRect.height = contentRect.bottom - contentRect.top;
return contentRect;
}
export function getContentRect(element, offsetParent, childTags) {
// Calculate this element's bounds
const contentRect = getElementRect(element, offsetParent);
// Get all immediate children
elements = getChildren(element, childTags);
return getContentRectRecursive(elements, offsetParent, childTags, contentRect);
}
export function getBoundingContentRect(element, offsetParent, childTags, scrollContainer = window) {
const elementRect = getContentRect(element, offsetParent, childTags);
if (scrollContainer) {
const scrollY = scrollContainer.pageYOffset || scrollContainer.scrollTop || 0;
const scrollX = scrollContainer.pageXOffset || scrollContainer.scrollLeft || 0;
elementRect.top -= scrollY;
elementRect.bottom -= scrollY;
elementRect.left -= scrollX;
elementRect.right -= scrollX;
}
return elementRect;
}