Robert Penner's easing equations for Scriptaculous 1.8

44 views
Skip to first unread message

Riccardo De Agostini

unread,
Apr 24, 2009, 5:37:14 AM4/24/09
to Prototype & script.aculo.us
Just thought this might be of interest to someone else. Comments and
optimizations are welcome.

//--------snip----snip----snip----snip-------------

//
==============================================================================
// Robert Penner's easing functions v2.0 (http://www.robertpenner.com/
easing)
// Ported to Scriptaculous 1.8 by Riccardo De Agostini (lozioric AT
gmail.com)
//
// Original terms of use (http://www.robertpenner.com/
easing_terms_of_use.html)
// also apply to this modification.
//
==============================================================================
//
// Penner's functions take a minimum of four parameters named t, b, c
and d,
// plus, in some cases, optional customization parameters (for
details, see
// http://www.robertpenner.com/easing/penner_chapter7_tweening.pdf)
//
// Scriptaculous' transitions are a simplified case of Penner's
functions,
// where b is always 0, c is always 1, and d is always 1. I've thus
simplified
// the original ActionScript code.
// I've also added some transformation functions, which can take an
easeIn, an
// easeOut or an easeIn / easeOut pair and turn them into a complete
set of
// transition functions. This obviously introduces some overhead, but
greatly
// simplifies the code.
//
// Simple usage example:
//
// new Effect.Move(myElement, {
// transition: Effect.Transitions.Cubic.easeInOut
// });
//
// Customization parameters, where present, may be used as follows:
//
// // No customization (use Penner's default value)
// new Effect.Move(myElement, {
// transition: Effect.Transitions.Back.easeIn
// });
//
// // Customized easing
// new Effect.Move(myElement, {
// transition: Effect.Transitions.Back.easeIn.custom(2.5)
// });
//
//
==============================================================================

Object.extend(Effect.Transitions, (function() {

//----------------------------------------------------------------------
// Function transformations
//----------------------------------------------------------------------

// easeIn to easeOut and vice versa
function reverse(eq, t)
{
return 1 - eq(1 - t);
}

// easeIn to easeInOut
function easeInToEaseInOut(easeIn, t)
{
t = 2 * t;
return 0.5 * (t < 1 ? easeIn(t) : 2 - easeIn(2 - t));
}

// easeOut to easeInOut
function easeOutToEaseInOut(easeOut, t)
{
t = 2 * t;
return 0.5 * (t < 1 ? 1 - easeOut(1 - t) : 1 + easeOut(t -
1));
}

// easeIn / easeOut pair to easeInOut
function easeInOutPairToEaseInOut(easeIn, easeOut, t)
{
t = 2 * t;
return 0.5 * (t < 1 ? easeIn(t) : 1 + easeOut(t - 1));
}

//----------------------------------------------------------------------
// Function set builders
//----------------------------------------------------------------------

// Build a function set from a complete set of easing functions
function functionSet(easeIn, easeOut, easeInOut)
{
return {
easeIn : easeIn,
easeOut : easeOut,
easeInOut: easeInOut
};
}

// Build a complete function set from just an easeIn
function functionSetFromEaseIn(easeIn)
{
return {
easeIn : easeIn,
easeOut : reverse.curry(easeIn),
easeInOut: easeInToEaseInOut.curry(easeIn)
};
}

// Build a complete function set from just an easeOut
function functionSetFromEaseOut(easeOut)
{
return {
easeIn : reverse.curry(easeOut),
easeOut : easeOut,
easeInOut: easeOutToEaseInOut.curry(easeOut)
};
}

// Build a complete function set from an easeIn / easeOut pair
function functionSetFromEaseInOutPair(easeIn, easeOut)
{
return {
easeIn : easeIn,
easeOut : easeOut,
easeInOut: easeInOutPairToEaseInOut.curry(easeIn, easeOut)
};
}

// Build a complete function set from just an easeIn,
// where the given function has custom parameters
function customizableFunctionSetFromEaseIn()
{
var args = $A(arguments);
var easeIn = args.shift();

function customEaseIn()
{
var args = [0].concat($A(arguments));

return function(t)
{
args[0] = t;
return easeIn.apply(this, args);
};
}

function customEaseOut()
{
return reverse.curry(customEaseIn.apply(this, arguments));
}

function customEaseInOut()
{
return easeInToEaseInOut.curry(customEaseIn.apply(this,
arguments));
}

var myEaseIn = customEaseIn.apply(this, args);
myEaseIn.custom = customEaseIn;
var myEaseOut = reverse.curry(myEaseIn);
myEaseOut.custom = customEaseOut;
var myEaseInOut = easeInToEaseInOut.curry(myEaseIn);
myEaseInOut.custom = customEaseInOut;

return {
easeIn : myEaseIn,
easeOut : myEaseOut,
easeInOut: myEaseInOut
};
}

//----------------------------------------------------------------------
// Penner's tween equations, simplified for Scriptaculous
//----------------------------------------------------------------------

function Quad_easeIn(t)
{
return t * t;
}

function Cubic_easeIn(t)
{
return t * t * t;
}

function Quart_easeIn(t)
{
return t * t * t * t;
}

function Quint_easeIn(t)
{
return t * t * t * t * t;
}

// This one is not Penner's: it's just a generalized case, for use
when
// i.e. Quad is too "soft" for your tastes but Cubic is too
"quick"
// (in this specific case you could use Pow.custom(2.5) for
example)
function Pow_easeIn(t, p)
{
return Math.pow(t, p);
}

function Back_easeIn(t, s)
{
return t * t * ((s + 1) * t - s);
}

// TODO (maybe): customize (customizableize? :-) ) this one
function Bounce_easeOut(t)
{
if (t < (1 / 2.75))
return 7.5625 * t * t;
if (t < (2 / 2.75))
return 7.5625 * (t-= (1.5 / 2.75)) * t + 0.75;
if (t < (2.5 / 2.75))
return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
}

function Circ_easeIn(t)
{
return -1 * (Math.sqrt(1 - t * t) - 1);
}

function Circ_easeOut(t)
{
t -= 1;
return Math.sqrt(1 - t * t);
}

function Elastic_easeIn(t, a, p)
{
if (t == 0) return 0;
if (t == 1) return 1;
if (a < 1)
{
a = 1;
var s = p / 4;
}
else
{
var s = p / (2 * Math.PI) * Math.asin(1 / a);
}
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) *
(2 * Math.PI) / p));
}

function Expo_easeIn(t)
{
return (t == 0) ? 0 : Math.pow(2, 10 * (t - 1));
}

function Expo_easeOut(t)
{
return (t == 1) ? 1 : 1 - Math.pow(2, -10 * t);
}

function Sine_easeIn(t)
{
return -1 * Math.cos(t * (Math.PI / 2)) + 1;
}

function Sine_easeOut(t)
{
return 1 * Math.sin(t * (Math.PI / 2));
}

function Sine_easeInOut(t)
{
return -0.5 * (Math.cos(Math.PI * t) - 1);
}

//----------------------------------------------------------------------
// Build and return the equation sets
//----------------------------------------------------------------------

return {
Quad : functionSetFromEaseIn(Quad_easeIn),
Cubic : functionSetFromEaseIn(Cubic_easeIn),
Quart : functionSetFromEaseIn(Quart_easeIn),
Quint : functionSetFromEaseIn(Quint_easeIn),
Pow : customizableFunctionSetFromEaseIn(Pow_easeIn, 2), //
Defaults to Quad
Back : customizableFunctionSetFromEaseIn(Back_easeIn,
1.70158),
Bounce : functionSetFromEaseOut(Bounce_easeOut),
Circ : functionSetFromEaseInOutPair(Circ_easeIn,
Circ_easeOut),
Elastic: customizableFunctionSetFromEaseIn(Elastic_easeIn, 1,
0.3),
Expo : functionSetFromEaseInOutPair(Expo_easeIn,
Expo_easeOut),
Sine : functionSet(Sine_easeIn, Sine_easeOut,
Sine_easeInOut)
};

})());

// EOF

//--------snip----snip----snip----snip-------------

Henry

unread,
Apr 24, 2009, 8:32:06 AM4/24/09
to Prototype & script.aculo.us
On Apr 24, 10:37 am, Riccardo De Agostini wrote:
<snip>
> function Sine_easeIn(t)
> {
> return -1 * Math.cos(t * (Math.PI / 2)) + 1;
> }

It is an underappreciated characteristic of ECMAScript that the
language does not contain such a thing as a negative numeric literal.
Instead constructs such as the "-1" above represent the unary negation
operator being applied to the positive numeric literal one. So
theoretically "-1" implies a runtime operation, though it is certainly
possible that an ECMAScript implementation could treat it as a
negative numeric literal (as doing so would not change the behaviour
of the code) and so only act on the "-" while compiling the code.

In any event, the expression "-1 * x" should always have the same
outcome as the expression "-x" and the latter would be one fewer
operations in ECMAScript terms (and even if the -1 is treated as a
negative numeric literal the unary negation operation should be faster
than the multiplication). Granted there won't be much in it
(especially against the unavoidable overheads of the function call),
but why not.

> function Sine_easeOut(t)
> {
> return 1 * Math.sin(t * (Math.PI / 2));
> }
<snip>

I cannot see any reason for multiplying any number by positive one.
That operation is sometimes used to force type-conversion (to numeric)
but that cannot be the reason here as the return value from - Math.sin
- is already guaranteed to be numeric.

There are (inevitably) a number of uses of - Math.PI * 2 - and -
Math.PI / 2 - in this code. It might be an idea to assign these two
values to variables in the containing scope and employ them as
constants in the functions. Apart from not repeating the math
operations each time they are used, the scope-chain lookup of the
variables should never be slower than the scope-chain lookup of - Math
- and so certainly will be faster than resolving - Math.PI -.

Riccardo De Agostini

unread,
Apr 26, 2009, 6:15:04 AM4/26/09
to Prototype & script.aculo.us
> In any event, the expression "-1 * x" should always have the same
> outcome as the expression "-x" and the latter would be one fewer
> operations in ECMAScript terms (and even if the -1 is treated as a
> negative numeric literal the unary negation operation should be faster
> than the multiplication).

> I cannot see any reason for multiplying any number by positive one.

My fault, in both cases above. The first thing I did when porting
Penner's code was sustituting 1 for all occurrences of the c and d
parameters and 0 for b, then I simplified the equations, but evidently
I forgot some 1 * <anything> here and there. I'm going to post a
revised version very soon.

> There are (inevitably) a number of uses of - Math.PI * 2 - and -
> Math.PI / 2 - in this code. It might be an idea to assign these two
> values to variables in the containing scope and employ them as
> constants in the functions.

Thumbs up. :-) This is coming in next version too.

Riccardo De Agostini

unread,
Apr 27, 2009, 2:51:38 AM4/27/09
to Prototype & script.aculo.us
A corrected version (as well as future versions, if any) can be found
here:
http://snipplr.com/view/14458/robert-penners-actionscript-easing-functions-ported-to-scriptaculous-18/
Reply all
Reply to author
Forward
0 new messages