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
|
/**
* Do callback when HTMLElement in removed
*
* @param {HTMLElement} element observing
* @param {Function} callback
*/
export const onRemove = (element, callback) => {
const parent = element.parentNode
if (!parent) throw new Error('The node must already be attached')
const obs = new window.MutationObserver(mutations => {
for (const mutation of mutations) {
for (const el of mutation.removedNodes) {
if (el === element) {
obs.disconnect()
callback()
}
}
}
})
obs.observe(parent, { childList: true })
}
/**
* Animate transition of DOMRect, with Web Animation API
*
* @param {HTMLElement} element Element which animation applies
* @param {DOMRect} rect DOMRect for transition
* @param {Object} options
* @param {Boolean} options.resume If true, transition starts from rect to DOMRect of element
* @param {Number} options.duration Duration of animation in milliseconds
* @returns {Animation} https://developer.mozilla.org/en-US/docs/Web/API/Animation
*/
export const animateRectTransition = (element, rect, options = {}) => {
if (!element.parentElement) { throw new Error('The node must already be attached') }
const { width: w1, height: h1, left: x1, top: y1 } = rect
const {
width: w2,
height: h2,
left: x2,
top: y2
} = element.getBoundingClientRect()
const rw = (w1 ?? w2) / w2
const rh = (h1 ?? h2) / h2
const dx = x1 - x2
const dy = y1 - y2
if ((dx === 0 && dy === 0) || !isFinite(rw) || !isFinite(rh)) {
return element.animate([], { duration: 0 })
}
const transform1 = 'translate(0, 0) scale(1, 1)'
const transform2 = `translate(${dx}px, ${dy}px) scale(${rw}, ${rh})`
const keyframes = [
{ transform: transform1, opacity: 1 },
{ transform: transform2, opacity: 0.3 }
]
if (options.resume === true) keyframes.reverse()
return element.animate(keyframes, {
duration: options.duration ?? 500,
easing: 'ease-in-out'
})
}
/**
* Throttle for function call
*
* @param {Function} func
* @param {Number} delay milliseconds
* @returns {Any} return value of function call, or null if throttled
*/
export function throttle (func, delay) {
let timerFlag = null
return function (...args) {
const context = this
if (timerFlag !== null) return null
timerFlag = setTimeout(
() => (timerFlag = null),
typeof delay === 'function' ? delay.call(context) : delay
)
return func.call(context, ...args)
}
}
/**
* shiftByWindow. make sure HTMLElement inside viewport
*
* @param {HTMLElement} element
*/
export const shiftByWindow = element => {
const rect = element.getBoundingClientRect()
const offsetX = window.innerWidth - rect.left - rect.width
const offsetY = window.innerHeight - rect.top - rect.height
element.style.transform = `translate(${offsetX < 0 ? offsetX : 0}px, ${offsetY < 0 ? offsetY : 0}px)`
}
/**
* insideWindow. check DOMRect is inside window
*
* @param {HTMLElement} element
*/
export const insideWindow = element => {
const rect = element.getBoundingClientRect()
return (
rect.left > 0 &&
rect.right < window.innerWidth + rect.width &&
rect.top > 0 &&
rect.bottom < window.innerHeight + rect.height
)
}
/**
* insideParent. check children element is inside DOMRect of parent element
*
* @param {HTMLElement} childElement
* @param {HTMLElement} parentElement
*/
export const insideParent = (childElement, parentElement) => {
const childRect = childElement.getBoundingClientRect()
const parentRect = parentElement.getBoundingClientRect()
const offset = 10
return (
childRect.left < parentRect.right - offset &&
childRect.right > parentRect.left + offset &&
childRect.top < parentRect.bottom - offset &&
childRect.bottom > parentRect.top + offset
)
}
|