2
2
* BezierEasing - use bezier curve for transition easing function
3
3
* by Gaëtan Renaudeau 2014 - 2015 – MIT License
4
4
*
5
- * Credits: is based on Firefox's nsSMILKeySpline.cpp
6
- * Usage:
7
- * var spline = BezierEasing([ 0.25, 0.1, 0.25, 1.0 ])
8
- * spline.get(x) => returns the easing value | x must be in [0, 1] range
9
- *
5
+ * @providesModule bezier
10
6
*/
11
7
12
8
// These values are established by empiricism with tests (tradeoff: performance VS precision)
@@ -18,21 +14,17 @@ var SUBDIVISION_MAX_ITERATIONS = 10;
18
14
var kSplineTableSize = 11 ;
19
15
var kSampleStepSize = 1.0 / ( kSplineTableSize - 1.0 ) ;
20
16
21
- var float32ArraySupported = typeof Float32Array === " function" ;
17
+ var float32ArraySupported = typeof Float32Array === ' function' ;
22
18
23
19
function A ( aA1 , aA2 ) { return 1.0 - 3.0 * aA2 + 3.0 * aA1 ; }
24
20
function B ( aA1 , aA2 ) { return 3.0 * aA2 - 6.0 * aA1 ; }
25
21
function C ( aA1 ) { return 3.0 * aA1 ; }
26
22
27
23
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
28
- function calcBezier ( aT , aA1 , aA2 ) {
29
- return ( ( A ( aA1 , aA2 ) * aT + B ( aA1 , aA2 ) ) * aT + C ( aA1 ) ) * aT ;
30
- }
24
+ function calcBezier ( aT , aA1 , aA2 ) { return ( ( A ( aA1 , aA2 ) * aT + B ( aA1 , aA2 ) ) * aT + C ( aA1 ) ) * aT ; }
31
25
32
26
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
33
- function getSlope ( aT , aA1 , aA2 ) {
34
- return 3.0 * A ( aA1 , aA2 ) * aT * aT + 2.0 * B ( aA1 , aA2 ) * aT + C ( aA1 ) ;
35
- }
27
+ function getSlope ( aT , aA1 , aA2 ) { return 3.0 * A ( aA1 , aA2 ) * aT * aT + 2.0 * B ( aA1 , aA2 ) * aT + C ( aA1 ) ; }
36
28
37
29
function binarySubdivide ( aX , aA , aB , mX1 , mX2 ) {
38
30
var currentX , currentT , i = 0 ;
@@ -49,111 +41,42 @@ function binarySubdivide (aX, aA, aB, mX1, mX2) {
49
41
}
50
42
51
43
function newtonRaphsonIterate ( aX , aGuessT , mX1 , mX2 ) {
52
- for ( var i = 0 ; i < NEWTON_ITERATIONS ; ++ i ) {
53
- var currentSlope = getSlope ( aGuessT , mX1 , mX2 ) ;
54
- if ( currentSlope === 0.0 ) return aGuessT ;
55
- var currentX = calcBezier ( aGuessT , mX1 , mX2 ) - aX ;
56
- aGuessT -= currentX / currentSlope ;
57
- }
58
- return aGuessT ;
44
+ for ( var i = 0 ; i < NEWTON_ITERATIONS ; ++ i ) {
45
+ var currentSlope = getSlope ( aGuessT , mX1 , mX2 ) ;
46
+ if ( currentSlope === 0.0 ) {
47
+ return aGuessT ;
48
+ }
49
+ var currentX = calcBezier ( aGuessT , mX1 , mX2 ) - aX ;
50
+ aGuessT -= currentX / currentSlope ;
51
+ }
52
+ return aGuessT ;
59
53
}
60
54
61
- /**
62
- * points is an array of [ mX1, mY1, mX2, mY2 ]
63
- */
64
- function BezierEasing ( points , b , c , d ) {
65
- if ( arguments . length === 4 ) {
66
- return new BezierEasing ( [ points , b , c , d ] ) ;
67
- }
68
- if ( ! ( this instanceof BezierEasing ) ) return new BezierEasing ( points ) ;
69
-
70
- if ( ! points || points . length !== 4 ) {
71
- throw new Error ( "BezierEasing: points must contains 4 values" ) ;
55
+ module . exports = function bezier ( mX1 , mY1 , mX2 , mY2 ) {
56
+ if ( ! ( 0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1 ) ) {
57
+ throw new Error ( 'bezier x values must be in [0, 1] range' ) ;
72
58
}
73
- for ( var i = 0 ; i < 4 ; ++ i ) {
74
- if ( typeof points [ i ] !== "number" || isNaN ( points [ i ] ) || ! isFinite ( points [ i ] ) ) {
75
- throw new Error ( "BezierEasing: points should be integers." ) ;
76
- }
77
- }
78
- if ( points [ 0 ] < 0 || points [ 0 ] > 1 || points [ 2 ] < 0 || points [ 2 ] > 1 ) {
79
- throw new Error ( "BezierEasing x values must be in [0, 1] range." ) ;
80
- }
81
-
82
- this . _str = "BezierEasing(" + points + ")" ;
83
- this . _css = "cubic-bezier(" + points + ")" ;
84
- this . _p = points ;
85
- this . _mSampleValues = float32ArraySupported ? new Float32Array ( kSplineTableSize ) : new Array ( kSplineTableSize ) ;
86
- this . _precomputed = false ;
87
-
88
- this . get = this . get . bind ( this ) ;
89
- }
90
-
91
- BezierEasing . prototype = {
92
59
93
- get : function ( x ) {
94
- var mX1 = this . _p [ 0 ] ,
95
- mY1 = this . _p [ 1 ] ,
96
- mX2 = this . _p [ 2 ] ,
97
- mY2 = this . _p [ 3 ] ;
98
- if ( ! this . _precomputed ) this . _precompute ( ) ;
99
- if ( mX1 === mY1 && mX2 === mY2 ) return x ; // linear
100
- // Because JavaScript number are imprecise, we should guarantee the extremes are right.
101
- if ( x === 0 ) return 0 ;
102
- if ( x === 1 ) return 1 ;
103
- return calcBezier ( this . _getTForX ( x ) , mY1 , mY2 ) ;
104
- } ,
105
-
106
- getPoints : function ( ) {
107
- return this . _p ;
108
- } ,
109
-
110
- toString : function ( ) {
111
- return this . _str ;
112
- } ,
113
-
114
- toCSS : function ( ) {
115
- return this . _css ;
116
- } ,
117
-
118
- // Private part
119
-
120
- _precompute : function ( ) {
121
- var mX1 = this . _p [ 0 ] ,
122
- mY1 = this . _p [ 1 ] ,
123
- mX2 = this . _p [ 2 ] ,
124
- mY2 = this . _p [ 3 ] ;
125
- this . _precomputed = true ;
126
- if ( mX1 !== mY1 || mX2 !== mY2 )
127
- this . _calcSampleValues ( ) ;
128
- } ,
129
-
130
- _calcSampleValues : function ( ) {
131
- var mX1 = this . _p [ 0 ] ,
132
- mX2 = this . _p [ 2 ] ;
60
+ // Precompute samples table
61
+ var sampleValues = float32ArraySupported ? new Float32Array ( kSplineTableSize ) : new Array ( kSplineTableSize ) ;
62
+ if ( mX1 !== mY1 || mX2 !== mY2 ) {
133
63
for ( var i = 0 ; i < kSplineTableSize ; ++ i ) {
134
- this . _mSampleValues [ i ] = calcBezier ( i * kSampleStepSize , mX1 , mX2 ) ;
64
+ sampleValues [ i ] = calcBezier ( i * kSampleStepSize , mX1 , mX2 ) ;
135
65
}
136
- } ,
137
-
138
- /**
139
- * getTForX chose the fastest heuristic to determine the percentage value precisely from a given X projection.
140
- */
141
- _getTForX : function ( aX ) {
142
- var mX1 = this . _p [ 0 ] ,
143
- mX2 = this . _p [ 2 ] ,
144
- mSampleValues = this . _mSampleValues ;
66
+ }
145
67
68
+ function getTForX ( aX ) {
146
69
var intervalStart = 0.0 ;
147
70
var currentSample = 1 ;
148
71
var lastSample = kSplineTableSize - 1 ;
149
72
150
- for ( ; currentSample !== lastSample && mSampleValues [ currentSample ] <= aX ; ++ currentSample ) {
73
+ for ( ; currentSample !== lastSample && sampleValues [ currentSample ] <= aX ; ++ currentSample ) {
151
74
intervalStart += kSampleStepSize ;
152
75
}
153
76
-- currentSample ;
154
77
155
78
// Interpolate to provide an initial guess for t
156
- var dist = ( aX - mSampleValues [ currentSample ] ) / ( mSampleValues [ currentSample + 1 ] - mSampleValues [ currentSample ] ) ;
79
+ var dist = ( aX - sampleValues [ currentSample ] ) / ( sampleValues [ currentSample + 1 ] - sampleValues [ currentSample ] ) ;
157
80
var guessForT = intervalStart + dist * kSampleStepSize ;
158
81
159
82
var initialSlope = getSlope ( guessForT , mX1 , mX2 ) ;
@@ -165,15 +88,18 @@ BezierEasing.prototype = {
165
88
return binarySubdivide ( aX , intervalStart , intervalStart + kSampleStepSize , mX1 , mX2 ) ;
166
89
}
167
90
}
168
- } ;
169
91
170
- // CSS mapping
171
- BezierEasing . css = {
172
- "ease" : BezierEasing . ease = BezierEasing ( 0.25 , 0.1 , 0.25 , 1.0 ) ,
173
- "linear" : BezierEasing . linear = BezierEasing ( 0.00 , 0.0 , 1.00 , 1.0 ) ,
174
- "ease-in" : BezierEasing . easeIn = BezierEasing ( 0.42 , 0.0 , 1.00 , 1.0 ) ,
175
- "ease-out" : BezierEasing . easeOut = BezierEasing ( 0.00 , 0.0 , 0.58 , 1.0 ) ,
176
- "ease-in-out" : BezierEasing . easeInOut = BezierEasing ( 0.42 , 0.0 , 0.58 , 1.0 )
92
+ return function ( x ) {
93
+ if ( mX1 === mY1 && mX2 === mY2 ) {
94
+ return x ; // linear
95
+ }
96
+ // Because JavaScript number are imprecise, we should guarantee the extremes are right.
97
+ if ( x === 0 ) {
98
+ return 0 ;
99
+ }
100
+ if ( x === 1 ) {
101
+ return 1 ;
102
+ }
103
+ return calcBezier ( getTForX ( x ) , mY1 , mY2 ) ;
104
+ } ;
177
105
} ;
178
-
179
- module . exports = BezierEasing ;
0 commit comments