UNPKG

5.75 kBJavaScriptView Raw
1/*
2fx
3*/"use strict"
4
5var prime = require("prime"),
6 requestFrame = require("./frame").request,
7 bezier = require("cubic-bezier")
8
9var map = require("prime/es5/array").map
10
11var sDuration = "([\\d.]+)(s|ms)?",
12 sCubicBezier = "cubic-bezier\\(([-.\\d]+),([-.\\d]+),([-.\\d]+),([-.\\d]+)\\)"
13
14var rDuration = RegExp(sDuration),
15 rCubicBezier = RegExp(sCubicBezier),
16 rgCubicBezier = RegExp(sCubicBezier, "g")
17
18 // equations collection
19
20var equations = {
21 "default" : "cubic-bezier(0.25, 0.1, 0.25, 1.0)",
22 "linear" : "cubic-bezier(0, 0, 1, 1)",
23 "ease-in" : "cubic-bezier(0.42, 0, 1.0, 1.0)",
24 "ease-out" : "cubic-bezier(0, 0, 0.58, 1.0)",
25 "ease-in-out" : "cubic-bezier(0.42, 0, 0.58, 1.0)"
26}
27
28equations.ease = equations["default"]
29
30var compute = function(from, to, delta){
31 return (to - from) * delta + from
32}
33
34var divide = function(string){
35 var numbers = []
36 var template = (string + "").replace(/[-.\d]+/g, function(number){
37 numbers.push(+number)
38 return "@"
39 })
40 return [numbers, template]
41}
42
43var Fx = prime({
44
45 constructor: function Fx(render, options){
46
47 // set options
48
49 this.setOptions(options)
50
51 // renderer
52
53 this.render = render || function(){}
54
55 // bound functions
56
57 var self = this
58
59 this.bStep = function(t){
60 return self.step(t)
61 }
62
63 this.bExit = function(time){
64 self.exit(time)
65 }
66
67 },
68
69 setOptions: function(options){
70 if (options == null) options = {}
71
72 if (!(this.duration = this.parseDuration(options.duration || "500ms"))) throw new Error("invalid duration")
73 if (!(this.equation = this.parseEquation(options.equation || "default"))) throw new Error("invalid equation")
74 this.callback = options.callback || function(){}
75
76 return this
77 },
78
79 parseDuration: function(duration){
80 if (duration = (duration + "").match(rDuration)){
81 var time = +duration[1],
82 unit = duration[2] || "ms"
83
84 if (unit === "s") return time * 1e3
85 if (unit === "ms") return time
86 }
87 },
88
89 parseEquation: function(equation, array){
90 var type = typeof equation
91
92 if (type === "function"){ // function
93 return equation
94 } else if (type === "string"){ // cubic-bezier string
95 equation = equations[equation] || equation
96 var match = equation.replace(/\s+/g, "").match(rCubicBezier)
97 if (match){
98 equation = map(match.slice(1), function(v){return +v})
99 if (array) return equation
100 if (equation.toString() === "0,0,1,1") return function(x){return x}
101 type = "object"
102 }
103 }
104
105 if (type === "object"){ // array
106 return bezier(equation[0], equation[1], equation[2], equation[3], 1e3 / 60 / this.duration / 4)
107 }
108 },
109
110 cancel: function(to){
111 this.to = to
112 this.cancelExit = requestFrame(this.bExit)
113 },
114
115 exit: function(time){
116 this.render(this.to)
117 delete this.cancelExit
118 this.callback(time)
119 },
120
121 start: function(from, to){
122
123 this.stop()
124
125 if (this.duration === 0){
126 this.cancel(to)
127 return this
128 }
129
130 this.isArray = false
131 this.isNumber = false
132
133 var fromType = typeof from,
134 toType = typeof to
135
136 if (fromType === "object" && toType === "object"){
137 this.isArray = true
138 } else if (fromType === "number" && toType === "number"){
139 this.isNumber = true
140 }
141
142 var from_ = divide(from),
143 to_ = divide(to)
144
145 this.from = from_[0]
146 this.to = to_[0]
147 this.templateFrom = from_[1]
148 this.templateTo = to_[1]
149
150 if (this.from.length !== this.to.length || this.from.toString() === this.to.toString()){
151 this.cancel(to)
152 return this
153 }
154
155 delete this.time
156 this.length = this.from.length
157 this.cancelStep = requestFrame(this.bStep)
158
159 return this
160
161 },
162
163 stop: function(){
164
165 if (this.cancelExit){
166 this.cancelExit()
167 delete this.cancelExit
168 } else if (this.cancelStep){
169 this.cancelStep()
170 delete this.cancelStep
171 }
172
173 return this
174 },
175
176 step: function(now){
177
178 this.time || (this.time = now)
179
180 var factor = (now - this.time) / this.duration
181
182 if (factor > 1) factor = 1
183
184 var delta = this.equation(factor),
185 from = this.from,
186 to = this.to,
187 tpl = this.templateTo
188
189 for (var i = 0, l = this.length; i < l; i++){
190 var f = from[i], t = to[i]
191 tpl = tpl.replace("@", t !== f ? compute(f, t, delta) : t)
192 }
193
194 this.render(this.isArray ? tpl.split(",") : this.isNumber ? +tpl : tpl, factor)
195
196 if (factor !== 1){
197 this.cancelStep = requestFrame(this.bStep)
198 } else {
199 delete this.cancelStep
200 this.callback(now)
201 }
202
203 }
204
205})
206
207var fx = function(render){
208
209 var ffx = new Fx(render)
210
211 return {
212
213 start: function(from, to, options){
214 var type = typeof options
215 ffx.setOptions((type === "function") ? {
216 callback: options
217 } : (type === "string" || type === "number") ? {
218 duration: options
219 } : options).start(from, to)
220 return this
221 },
222
223 stop: function(){
224 ffx.stop()
225 return this
226 }
227
228 }
229
230}
231
232fx.prototype = Fx.prototype
233
234module.exports = fx