-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathanimate.js
225 lines (196 loc) · 5.57 KB
/
animate.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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
'use strict'
const secret = {
entering: Symbol('during entering animation'),
leaving: Symbol('during leaving animation'),
position: Symbol('animated element position'),
parent: Symbol('parent node of leaving node'),
listening: Symbol('listening for animationend')
}
const watchedNodes = new Map()
let checkQueued = false
function onAnimationEnd (ev) {
const elem = ev.target
if (elem[secret.leaving]) {
elem.remove()
}
if (elem[secret.entering]) {
elem.style.animation = ''
elem[secret.entering] = false
}
}
function animate (elem) {
if (elem.nodeType !== 1) return
elem.$attribute('enter-animation', enterAttribute)
elem.$attribute('leave-animation', leaveAttribute)
elem.$attribute('move-animation', moveAttribute)
queueCheck()
elem.$cleanup(queueCheck)
}
animate.$name = 'animate'
animate.$require = ['attributes']
module.exports = animate
function enterAttribute (animation) {
if (this[secret.entering] !== false) {
this[secret.entering] = true
if (typeof animation === 'object' && animation) {
animation = animationObjectToString(animation)
} else if (typeof animation === 'string') {
animation = animation
}
this.style.animation = animation
setAnimationDefaults(this)
registerListener(this)
}
}
function leaveAttribute (animation) {
if (!this[secret.parent]) {
this[secret.parent] = this.parentNode
this.$cleanup(onLeave, animation)
}
getPosition(this)
registerListener(this)
}
function getPosition (node) {
let position = watchedNodes.get(node)
if (!position) {
position = {}
watchedNodes.set(node, position)
node.$cleanup(unwatch)
}
return position
}
function registerListener (elem) {
const root = elem.$root
if (!root[secret.listening]) {
root.addEventListener('animationend', onAnimationEnd, true)
root[secret.listening] = true
}
}
function onLeave (animation) {
this[secret.leaving] = true
if (typeof animation === 'object' && animation) {
animation = animationObjectToString(animation)
} else if (typeof animation === 'string') {
animation = animation
}
this.style.animation = animation
setAnimationDefaults(this)
this[secret.parent].appendChild(this)
if (shouldAbsolutePosition(this)) {
toAbsolutePosition(this)
}
}
function moveAttribute (transition) {
const position = getPosition(this)
position.move = true
if (typeof transition === 'object' && transition) {
transition = 'transform ' + transitionObjectToString(transition)
} else if (typeof transition === 'string') {
transition = 'transform ' + transition
} else {
transition = 'transform'
}
this.style.transition = transition
setTransitionDefaults(this)
}
function unwatch () {
watchedNodes.delete(this)
}
function queueCheck () {
if (!checkQueued) {
checkQueued = true
requestAnimationFrame(checkWatchedNodes)
requestAnimationFrame(moveWatchedNodes)
}
}
function checkWatchedNodes () {
watchedNodes.forEach(checkWatchedNode)
checkQueued = false
}
function checkWatchedNode (position, node) {
const prevTop = position.top
const prevLeft = position.left
position.top = node.offsetTop
position.left = node.offsetLeft
position.height = node.offsetHeight
position.width = node.offsetWidth + 1
position.xDiff = (prevLeft - position.left) || 0
position.yDiff = (prevTop - position.top) || 0
}
function moveWatchedNodes () {
watchedNodes.forEach(moveWatchedNode)
}
function moveWatchedNode (position, node) {
if (position.move) {
const style = node.style
const transition = style.transition
style.transition = ''
style.transform = `translate(${position.xDiff}px, ${position.yDiff}px)`
requestAnimationFrame(() => {
style.transition = transition
style.transform = ''
})
}
}
function animationObjectToString (animation) {
return [
animation.name,
timeToString(animation.duration),
animation.timingFunction,
timeToString(animation.delay),
animation.iterationCount,
animation.direction,
animation.fillMode,
boolToPlayState(animation.playState)
].join(' ')
}
function transitionObjectToString (transition) {
return [
timeToString(transition.duration),
timeToString(transition.delay),
transition.timingFunction
].join(' ')
}
function setAnimationDefaults (elem) {
const style = elem.style
const duration = style.animationDuration
const fillMode = style.animationFillMode
if (duration === 'initial' || duration === '' || duration === '0s') {
style.animationDuration = '1s'
}
if (fillMode === 'initial' || fillMode === '' || fillMode === 'none') {
style.animationFillMode = 'both'
}
}
function setTransitionDefaults (elem) {
const style = elem.style
const duration = style.transitionDuration
if (duration === 'initial' || duration === '' || duration === '0s') {
style.transitionDuration = '1s'
}
}
function shouldAbsolutePosition (elem) {
elem = elem.parentNode
while (elem && elem !== elem.$root) {
if (elem[secret.leaving]) return false
elem = elem.parentNode
}
return true
}
function toAbsolutePosition (elem) {
const style = elem.style
const position = watchedNodes.get(elem)
style.top = style.top || `${position.top}px`
style.left = style.left || `${position.left}px`
style.width = `${position.width}px`
style.height = `${position.height}px`
style.margin = '0'
style.boxSizing = 'border-box'
style.position = 'absolute'
}
function timeToString (time) {
return (typeof time === 'number') ? time + 'ms' : time
}
function boolToPlayState (bool) {
return (bool === false || bool === 'paused') ? 'paused' : 'running'
}