-
Notifications
You must be signed in to change notification settings - Fork 3
/
timer.es6.js
208 lines (165 loc) · 5.78 KB
/
timer.es6.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
import TimerState from './lib/timerstate.js'
class Timer {
constructor (config) {
// Clean config
if (config === +config) { config = { duration: config }}
config = Object.assign({ constrain: true }, config)
// You access that property at your own risk
Object.defineProperty(this, '__STATE__', {
value: new TimerState(),
enumerable: false
})
// Initialize the object
this.__STATE__.duration = config.duration
this.__STATE__.delay = config.delay
this.__STATE__.easing = config.easing
this.__STATE__.speed = config.speed
this.__STATE__.loopLength = config.loops
this.__STATE__.steps = config.steps
this.__STATE__.constrain = config.constrain
}
// PROPERTIES
// --------------------------------------------------------------------------
// Timer.startTime
set startTime (value) { throw new Error("Timer.startTime is a readonly property") }
get startTime () { return this.__STATE__.userTime }
// Timer.delay
set delay (value) { this.__STATE__.delay = value }
get delay () { return this.__STATE__.delay }
// Timer.speed
set speed (value) { this.__STATE__.speed = value }
get speed () { return this.__STATE__.speed }
// Timer.duration
set duration (value) { this.__STATE__.duration = value }
get duration () { return this.__STATE__.duration }
// Timer.easing
set easing (value) { this.__STATE__.easing = value }
get easing () { return this.__STATE__.easing.ease }
// Timer.constrain
set constrain (value) { this.__STATE__.constrain = value }
get constrain () { return this.__STATE__.constrain }
// Timer.loops
set loops (value) { this.__STATE__.loopLength = value }
get loops () { return this.__STATE__.loopLength }
// Timer.steps.length
// Timer.steps.position
set steps (value) { this.__STATE__.steps = value }
get steps () {
var timer = this
return {
set length(value) { timer.__STATE__.steps = { length: value } },
get length() { return timer.__STATE__.steps.length },
set position(value) { timer.__STATE__.steps = { position: value } },
get position() { return timer.__STATE__.steps.position }
}
}
// Timer.is.playing
// Timer.is.paused
set is (value) { throw new Error("Timer.is is a readonly property") }
get is () {
var startTime = this.__STATE__.startTime,
speed = this.__STATE__.speed,
output = {
playing: startTime !== null,
paused : startTime !== null && speed === 0
}
if (this.constrain) {
if (speed > 0 && this.position.time >= 1) { this.stop() }
if (speed < 0 && this.position.time <= 0) { this.stop() }
}
return Object.freeze(output)
}
// Timer.position.time
// Timer.position.value
// Timer.position.loop
set position (value) { throw new Error("Timer.position is a readonly property") }
get position () {
var begin, end, now, ease, speed, dur, pos,
startTime = this.__STATE__.startTime,
output = {
value : 0,
time : 0,
loop : 1
}
if (startTime === null) { return output }
speed = this.__STATE__.speed
dur = this.__STATE__.duration
now = Date.now()
if (speed === 0) {
now = this.__STATE__.pauseTime
}
else if (speed < 0) {
now = this.__STATE__.backTime * 2 - now
}
begin = this.__STATE__.begin
end = this.__STATE__.end
pos = Math.ceil((now - begin) / dur)
if (pos < 1) { pos = 1 }
if (this.loops > 0 && pos > this.loops) { pos = this.loops }
begin += (pos - 1) * dur
end += (pos - 1) * dur
output.loop = pos
if (this.constrain) {
if (now <= begin) { return output }
if (now > end) { this.stop(); return output }
}
ease = this.__STATE__.easing
output.time = ease.getTime(begin, end, now)
if (this.steps.length > 0) {
let fn = this.steps.position === "end" ? "floor" : "ceil"
let step = Math[fn](output.time * this.steps.length)
output.value = ease.ease(step / this.steps.length)
} else {
output.value = ease.getValue(begin, end, now)
}
return Object.freeze(output)
}
// METHODS
// --------------------------------------------------------------------------
play () {
if (this.__STATE__.userTime === null) {
this.__STATE__.userTime = Date.now()
} else if (this.__STATE__.speed === 0) {
this.__STATE__.speed = this.__STATE__.prevSpeed
}
}
pause () {
this.speed = 0
}
stop () {
this.__STATE__.userTime = null
}
freeze() {
if (arguments.length < 2 && this.startTime === null) {
throw new Error('The timer must be started with the play() function first')
}
var begin = (arguments.length > 1 ? arguments[0] : this.startTime) + this.delay,
now = arguments[1] || arguments[0] || Date.now(),
end = begin + this.duration,
step = this.steps.position === "end" ? "floor" : "ceil",
time = this.__STATE__.easing.getTime(begin, end, now),
value = this.__STATE__.easing.getValue(begin, end, now)
if (this.steps.length > 0) {
let stepVal = Math[step](time * this.steps.length)
value = this.__STATE__.easing.ease(stepVal / this.steps.length)
}
if (this.constrain) {
if (time < 0) { return { time: 0, value: 0 } }
if (time > 1) { return { time: 1, value: 1 } }
}
return { time, value }
}
}
Object.defineProperties(Timer.prototype, {
'startTime': { enumerable: true },
'delay' : { enumerable: true },
'speed' : { enumerable: true },
'duration' : { enumerable: true },
'easing' : { enumerable: true },
'constrain': { enumerable: true },
'loops' : { enumerable: true },
'steps' : { enumerable: true },
'is' : { enumerable: true },
'position' : { enumerable: true }
})
export { Timer }