├── .npmignore ├── .travis.yml ├── package.json ├── index.js ├── README.md └── test └── test.js /.npmignore: -------------------------------------------------------------------------------- 1 | test/test.js 2 | README.md 3 | 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Arian Stolwijk", 3 | "name": "cubic-bezier", 4 | "description": "A small cubic bézier timing function", 5 | "version": "0.1.2", 6 | "main": "index", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/arian/cubic-bezier.git" 10 | }, 11 | "scripts": { 12 | "test": "node test/test.js" 13 | }, 14 | "dependencies": {}, 15 | "devDependencies": {} 16 | } 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(x1, y1, x2, y2, epsilon){ 3 | 4 | var curveX = function(t){ 5 | var v = 1 - t; 6 | return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t; 7 | }; 8 | 9 | var curveY = function(t){ 10 | var v = 1 - t; 11 | return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t; 12 | }; 13 | 14 | var derivativeCurveX = function(t){ 15 | var v = 1 - t; 16 | return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (- t * t * t + 2 * v * t) * x2; 17 | }; 18 | 19 | return function(t){ 20 | 21 | var x = t, t0, t1, t2, x2, d2, i; 22 | 23 | // First try a few iterations of Newton's method -- normally very fast. 24 | for (t2 = x, i = 0; i < 8; i++){ 25 | x2 = curveX(t2) - x; 26 | if (Math.abs(x2) < epsilon) return curveY(t2); 27 | d2 = derivativeCurveX(t2); 28 | if (Math.abs(d2) < 1e-6) break; 29 | t2 = t2 - x2 / d2; 30 | } 31 | 32 | t0 = 0, t1 = 1, t2 = x; 33 | 34 | if (t2 < t0) return curveY(t0); 35 | if (t2 > t1) return curveY(t1); 36 | 37 | // Fallback to the bisection method for reliability. 38 | while (t0 < t1){ 39 | x2 = curveX(t2); 40 | if (Math.abs(x2 - x) < epsilon) return curveY(t2); 41 | if (x > x2) t0 = t2; 42 | else t1 = t2; 43 | t2 = (t1 - t0) * .5 + t0; 44 | } 45 | 46 | // Failure 47 | return curveY(t2); 48 | 49 | }; 50 | 51 | }; 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Cubic Bézier solver 2 | =================== 3 | 4 | A small cubic bézier timing function like CSS3 timing functions. 5 | 6 | [![Build Status](https://secure.travis-ci.org/arian/LISP.js.png)](http://travis-ci.org/arian/cubic-bezier) 7 | 8 | Install 9 | ------- 10 | 11 | npm install cubic-bezier 12 | 13 | Usage 14 | ----- 15 | 16 | ``` js 17 | var timingFunction = bezier(p1x, p1y, p2x, p2y, epsilon); 18 | 19 | // epsilon determines the precision of the solved values 20 | // a good approximation is: 21 | var duration = 200; // duration of animation in milliseconds. 22 | var epsilon = (1000 / 60 / duration) / 4; 23 | 24 | var easeIn = bezier(0.42, 0, 1.0, 1.0, epsilon); 25 | var linear = bezier(0, 0, 1, 1, epsilon); 26 | 27 | for (var t = 0; t <= 1; t += 0.001){ 28 | console.log(easeIn(t)); 29 | console.log(linear(t)); 30 | } 31 | ``` 32 | 33 | ## MIT License 34 | 35 | Copyright (c) 2013 Arian Stolwijk 36 | 37 | Permission is hereby granted, free of charge, to any person obtaining a copy of 38 | this software and associated documentation files (the "Software"), to deal in 39 | the Software without restriction, including without limitation the rights to 40 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 41 | of the Software, and to permit persons to whom the Software is furnished to do 42 | so, subject to the following conditions: 43 | 44 | The above copyright notice and this permission notice shall be included in all 45 | copies or substantial portions of the Software. 46 | 47 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 48 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 49 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 50 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 51 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 52 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 53 | SOFTWARE. 54 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 2 | var bezier = require('../index'); 3 | var assert = require('assert'); 4 | 5 | var round = function(x, n){ 6 | var n = Math.pow(10, n); 7 | return Math.round(x * n) / n; 8 | }; 9 | 10 | var pass = function(msg){ 11 | console.log('\033[32m✓ ' + msg + '\033[0m'); 12 | }; 13 | 14 | var fail = function(msg, err){ 15 | console.log('\033[31m✘ ' + msg + '\033[0m'); 16 | if (err) console.log(err); 17 | }; 18 | 19 | var failures = 0; 20 | 21 | // easeIn 22 | try { 23 | var easeIn = bezier(0.42, 0, 1, 1, 1 / 100); 24 | 25 | assert.equal(0, easeIn(0)); 26 | assert.equal(1, easeIn(1)); 27 | assert.equal(0.2161066, round(easeIn(0.4), 7)); 28 | assert.equal(0.4243386, round(easeIn(0.6), 7)); 29 | 30 | pass('easeIn'); 31 | } catch (e) { 32 | failures++; 33 | fail('easeIn', e); 34 | } 35 | 36 | // Linear 37 | try { 38 | var linear = bezier(0, 0, 1, 1, 1 / 100); 39 | 40 | assert.equal(0, linear(0)); 41 | assert.equal(1, linear(1)); 42 | assert.equal(0.4081584, round(linear(0.4), 7)); 43 | assert.equal(0.6067481, round(linear(0.6), 7)); 44 | 45 | pass('linear'); 46 | } catch (e) { 47 | failures++; 48 | fail('linear', e); 49 | } 50 | 51 | 52 | // easeInOut with epsilon argument 53 | try { 54 | var duration = 200; 55 | var easeInOut = bezier(0.42, 0, 0.58, 1, (1000 / 60 / duration) / 4); 56 | 57 | assert.equal(0, easeInOut(0)); 58 | assert.equal(1, easeInOut(1)); 59 | assert.equal(0.352, round(easeInOut(0.4), 7)); 60 | assert.equal(0.648, round(easeInOut(0.6), 7)); 61 | 62 | pass('easeInOut with epsilon argument'); 63 | } catch (e) { 64 | failures++; 65 | fail('easeInOut', e); 66 | } 67 | 68 | 69 | // This should trigger the binary search algorithm. 70 | try { 71 | var overshoot = bezier(.42, 1.33, .8, 1.44, 1e-6); 72 | 73 | assert.equal(0, overshoot(0)); 74 | assert.equal(1, overshoot(1)); 75 | assert.equal(0.9458252, round(overshoot(0.4), 7)); 76 | assert.equal(1.1771389, round(overshoot(0.6), 7)); 77 | assert.equal(1.1568624, round(overshoot(0.9), 7)); 78 | 79 | pass('overshoot for binary search algorithm'); 80 | } catch (e) { 81 | failures++; 82 | fail('overshoot', e); 83 | } 84 | 85 | assert.equal(0, failures, 'there shouldn\'t be any failures'); 86 | 87 | pass('Cubic bézier tests passed'); 88 | 89 | 90 | --------------------------------------------------------------------------------