Skip to content

Instantly share code, notes, and snippets.

@mckamey
Created September 25, 2012 16:35
Show Gist options
  • Save mckamey/3783009 to your computer and use it in GitHub Desktop.
Save mckamey/3783009 to your computer and use it in GitHub Desktop.
JavaScript port of Webkit CSS cubic-bezier(p1x.p1y,p2x,p2y) and various approximations
/*
* Copyright (C) 2008 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* JavaScript port of Webkit implementation of CSS cubic-bezier(p1x.p1y,p2x,p2y) by http://mck.me
* http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/UnitBezier.h
*/
var Bezier = (function(){
'use strict';
/**
* Duration value to use when one is not specified (400ms is a common value).
* @const
* @type {number}
*/
var DEFAULT_DURATION = 400;//ms
/**
* The epsilon value we pass to UnitBezier::solve given that the animation is going to run over |dur| seconds.
* The longer the animation, the more precision we need in the timing function result to avoid ugly discontinuities.
* http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/page/animation/AnimationBase.cpp
*/
var solveEpsilon = function(duration) {
return 1.0 / (200.0 * duration);
};
/**
* Defines a cubic-bezier curve given the middle two control points.
* NOTE: first and last control points are implicitly (0,0) and (1,1).
* @param p1x {number} X component of control point 1
* @param p1y {number} Y component of control point 1
* @param p2x {number} X component of control point 2
* @param p2y {number} Y component of control point 2
*/
var unitBezier = function(p1x, p1y, p2x, p2y) {
// private members --------------------------------------------
// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
/**
* X component of Bezier coefficient C
* @const
* @type {number}
*/
var cx = 3.0 * p1x;
/**
* X component of Bezier coefficient B
* @const
* @type {number}
*/
var bx = 3.0 * (p2x - p1x) - cx;
/**
* X component of Bezier coefficient A
* @const
* @type {number}
*/
var ax = 1.0 - cx -bx;
/**
* Y component of Bezier coefficient C
* @const
* @type {number}
*/
var cy = 3.0 * p1y;
/**
* Y component of Bezier coefficient B
* @const
* @type {number}
*/
var by = 3.0 * (p2y - p1y) - cy;
/**
* Y component of Bezier coefficient A
* @const
* @type {number}
*/
var ay = 1.0 - cy - by;
/**
* @param t {number} parametric timing value
* @return {number}
*/
var sampleCurveX = function(t) {
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
return ((ax * t + bx) * t + cx) * t;
};
/**
* @param t {number} parametric timing value
* @return {number}
*/
var sampleCurveY = function(t) {
return ((ay * t + by) * t + cy) * t;
};
/**
* @param t {number} parametric timing value
* @return {number}
*/
var sampleCurveDerivativeX = function(t) {
return (3.0 * ax * t + 2.0 * bx) * t + cx;
};
/**
* Given an x value, find a parametric value it came from.
* @param x {number} value of x along the bezier curve, 0.0 <= x <= 1.0
* @param epsilon {number} accuracy limit of t for the given x
* @return {number} the t value corresponding to x
*/
var solveCurveX = function(x, epsilon) {
var t0;
var t1;
var t2;
var x2;
var d2;
var i;
// First try a few iterations of Newton's method -- normally very fast.
for (t2 = x, i = 0; i < 8; i++) {
x2 = sampleCurveX(t2) - x;
if (Math.abs (x2) < epsilon) {
return t2;
}
d2 = sampleCurveDerivativeX(t2);
if (Math.abs(d2) < 1e-6) {
break;
}
t2 = t2 - x2 / d2;
}
// Fall back to the bisection method for reliability.
t0 = 0.0;
t1 = 1.0;
t2 = x;
if (t2 < t0) {
return t0;
}
if (t2 > t1) {
return t1;
}
while (t0 < t1) {
x2 = sampleCurveX(t2);
if (Math.abs(x2 - x) < epsilon) {
return t2;
}
if (x > x2) {
t0 = t2;
} else {
t1 = t2;
}
t2 = (t1 - t0) * 0.5 + t0;
}
// Failure.
return t2;
};
/**
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param epsilon {number} the accuracy of t for the given x
* @return {number} the y value along the bezier curve
*/
var solve = function(x, epsilon) {
return sampleCurveY(solveCurveX(x, epsilon));
};
// public interface --------------------------------------------
/**
* Find the y of the cubic-bezier for a given x with accuracy determined by the animation duration.
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
return function(x, duration) {
return solve(x, solveEpsilon(+duration || DEFAULT_DURATION));
};
};
// http://www.w3.org/TR/css3-transitions/#transition-timing-function
return {
/**
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
linear: unitBezier(0.0, 0.0, 1.0, 1.0),
/**
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
ease: unitBezier(0.25, 0.1, 0.25, 1.0),
/**
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
easeIn: unitBezier(0.42, 0, 1.0, 1.0),
/**
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
easeOut: unitBezier(0, 0, 0.58, 1.0),
/**
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
easeInOut: unitBezier(0.42, 0, 0.58, 1.0),
/**
* @param p1x {number} X component of control point 1
* @param p1y {number} Y component of control point 1
* @param p2x {number} X component of control point 2
* @param p2y {number} Y component of control point 2
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
cubicBezier: function(p1x, p1y, p2x, p2y, x, duration) {
return unitBezier(p1x, p1y, p2x, p2y)(x, duration);
}
};
})();
/**
* Various fast approximations and alternates to cubic-bezier easing functions.
* http://www.w3.org/TR/css3-transitions/#transition-timing-function
*/
var Easing = (function(){
'use strict';
/**
* @const
*/
var EASE_IN_OUT_CONST = 0.5 * Math.pow(0.5, 1.925);
return {
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
linear: function(x) {
return x;
},
// /**
// * @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
// * @return {number} the y value along the curve
// */
// ease: function(x) {
// // TODO: find fast approximations
// return x;
// },
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInApprox: function(x) {
// very close approximation to cubic-bezier(0.42, 0, 1.0, 1.0)
return Math.pow(x, 1.685);
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInQuadratic: function(x) {
return (x * x);
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInCubic: function(x) {
return (x * x * x);
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeOutApprox: function(x) {
// very close approximation to cubic-bezier(0, 0, 0.58, 1.0)
return 1 - Math.pow(1-x, 1.685);
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeOutQuadratic: function(x) {
x -= 1;
return 1 - (x * x);
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeOutCubic: function(x) {
x -= 1;
return 1 + (x * x * x);
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInOutApprox: function(x) {
// very close approximation to cubic-bezier(0.42, 0, 0.58, 1.0)
if (x < 0.5) {
return EASE_IN_OUT_CONST * Math.pow(x, 1.925);
} else {
return 1 - EASE_IN_OUT_CONST * Math.pow(1-x, 1.925);
}
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInOutQuadratic: function(x) {
if (x < 0.5) {
return (2 * x * x);
} else {
x -= 1;
return 1 - (2 * x * x);
}
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInOutCubic: function(x) {
if (x < 0.5) {
return (4 * x * x * x);
} else {
x -= 1;
return 1 + (4 * x * x * x);
}
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInOutQuartic: function(x) {
if (x < 0.5) {
return (8 * x * x * x * x);
} else {
x -= 1;
return 1 + (8 * x * x * x * x);
}
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInOutQuintic: function(x) {
if (x < 0.5) {
return (16 * x * x * x * x * x);
} else {
x -= 1;
return 1 + (16 * x * x * x * x * x);
}
}
};
})();
@mietek
Copy link

mietek commented Sep 10, 2014

Did you eventually find a fast approximation to ease?

@mietek
Copy link

mietek commented Sep 12, 2014

Guess not. Here's mine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment