/**
* VERSION: 0.73
* DATE: 2011-12-27
* AS2
* UPDATES AND DOCS AT: http://www.greensock.com
**/
import com.greensock.TweenLite;
import com.greensock.core.SimpleTimeline;
import com.greensock.plugins.TweenPlugin;
import com.greensock.plugins.helpers.ThrowProp;
/**
* ThrowPropsPlugin
allows you to simply define an initial velocity for a property
* (or multiple properties) as well as optional maximum and/or minimum end values and then it
* will calculate the appropriate landing position and plot a smooth course based on the easing
* equation you define (Quad.easeOut by default, as set in TweenLite). This is perfect
* for flick-scrolling or animating things as though they are being thrown.
*
* For example, let's say a user clicks and drags a ball and you track its velocity using an
* onEnterFrame
handler and then when the user releases the mouse button, you'd determine
* the velocity but you can't do a normal tween because you don't know exactly where it should
* land or how long the tween should last (faster initial velocity would mean a longer duration).
* You need the tween to pick up exactly where the user left off so that it appears to smoothly continue
* moving in the same direction and at the same velocity they were dragging and then decelerate
* based on whatever ease you define in your tween.
*
* Oh, and one more challenge: maybe you want the final resting value to always lie within a
* particular range so that things don't land way off the edge of the screen. But you don't want
* it to suddenly jerk to a stop when it hits the edge of the screen; instead, you want it to ease
* gently into place even if that means going past the landing spot briefly and easing back
* (if the initial velocity is fast enough to require that). The whole point is to make it look smooth.
*
* No problem.
*
* In its simplest form, you can pass just the initial velocity for each property like this:
*
* TweenLite.to(mc, 2, {throwProps:{_x:500, _y:-300}});
*
* In the above example, mc._x
will animate at 500 pixels per second initially and
* mc._y
will animate at -300 pixels per second. Both will decelerate smoothly
* until they come to rest 2 seconds later (because the tween's duration is 2 seconds).
*
* To use the Strong.easeOut
easing equation and impose maximum and minimum boundaries on
* the end values, use the object syntax with the max
and min
special
* properties like this:
*
* TweenLite.to(mc, 2, {throwProps:{_x:{velocity:500, max:1024, min:0}, _y:{velocity:-300, max:720, min:0}}, ease:Strong.easeOut});
*
*
* Notice the nesting of the objects ({}). The max
and min
values refer
* to the range for the final resting position (coordinates in this case), NOT the velocity.
* So mc._x
would always land between 0 and 1024 in this case, and mc._y
* would always land between 0 and 720. If you want the target object to land on a specific value
* rather than within a range, simply set max
and min
to identical values.
* Also notice that you must define a velocity
value for each property.
*
* ThrowPropsPlugin isn't just for tweening _x and _y coordinates. It works with any numeric property,
* so you could use it for spinning the _rotation
of an object as well. Or the
* _xscale
/_yscale
properties. Maybe the user drags to spin a wheel and
* lets go and you want it to continue increasing the _rotation
at that velocity,
* decelerating smoothly until it stops.
*
* One of the trickiest parts of creating a throwProps
tween that looks fluid and natural,
* particularly if you're applying maximum and/or minimum values, is determining its duration.
* Typically it's best to have a relatively consistent level of resistance so that if the
* initial velocity is very fast, it takes longer for the object to come to rest compared to
* when the initial velocity is slower. You also may want to impose some restrictions on how long
* a tween can last (if the user drags incredibly fast, you might not want the tween to last 200
* seconds). The duration will also affect how far past a max/min boundary the property can potentially
* go, so you might want to only allow a certain amount of overshoot tolerance. That's why ThrowPropsPlugin
* has a few static helper methods that make managing all these variables much easier. The one you'll
* probably use most often is the to()
method which is very similar
* to TweenLite.to()
except that it doesn't have a duration
parameter and
* it adds several other optional parameters. Read the docs below for details.
*
* Feel free to experiment with using different easing equations to control how the values ease into
* place at the end. You don't need to put the "ease" special property inside the
* throwProps
object. Just keep it in the same place it has always been, like:
*
* TweenLite.to(mc, 1, {throwProps:{_x:500, _y:-300}, ease:Strong.easeOut});
*
* A unique convenience of ThrowPropsPlugin compared to most other solutions out there which use
* onEnterFrame
loops is that everything is reverseable and you can jump to any spot
* in the tween immediately. So if you create several throwProps
tweens, for example, and
* dump them into a TimelineLite, you could simply call reverse()
on the timeline
* to watch the objects retrace their steps right back to the beginning.
*
* (note: it is best to use an easeOut
with throwProps
tweens, but
* you can vary the strength by using different flavors like Strong.easeOut, Cubic.easeOut,
* Quad.easeOut, Back.easeOut
, etc.)
*
* The following example creates a Sprite (mc
), populates it with a long TextField
* and makes it vertically draggable. Then it tracks its velocity in an onEnterFrame
* handler and then allows it to be thrown within the bounds defined by the bounds
* rectangle, smoothly easing into place regardless of where and how fast it is thrown:
@example Example AS3 code:
resistance
that is used to calculate how long it will take
* for the tweening property (or properties) to come to rest by the static ThrowPropsPlugin.to()
* and ThrowPropsPlugin.calculateTweenDuration()
methods. Keep in mind that you can define
* a resistance
value either for each individual property in the throwProps
tween
* like this:
*
* ThrowPropsPlugin.to(mc, {throwProps:{_x:{velocity:500, resistance:150}, _y:{velocity:-300, resistance:50}}});
*
resistance
value that will be used for all of the
* properties in that particular throwProps
tween like this:
*
* ThrowPropsPlugin.to(mc, {throwProps:{_x:500, _y:-300, resistance:150}});
*
* //-OR-
*
* ThrowPropsPlugin.to(mc, {throwProps:{_x:{velocity:500, max:800, min:0}, _y:{velocity:-300, max:800, min:100}, resistance:150}});
*
velocity
, max
, min
,
* and resistance
) and creates a TweenLite instance with the appropriate duration. You can use
* ThrowPropsPlugin.to()
instead of TweenLite.to()
to create
* a tween - they're identical except that ThrowPropsPlugin.to()
doesn't have a
* duration
parameter (it figures it out for you) and it adds a few extra parameters
* to the end that can optionally be used to affect the duration. ThrowPropsPlugin.to()
will recognize the
* resistance
special property which basically controls how quickly each
* property's velocity decelerates (and consequently influences the duration of the tween).
* For example, if the initial velocity
is 500 and the resistance
* is 300, it will decelerate much faster than if the resistance was 20. You can define
* a resistance
value either for each individual property in the throwProps
* tween like this:
*
* ThrowPropsPlugin.to(mc, {throwProps:{_x:{velocity:500, resistance:150}, _y:{velocity:-300, resistance:50}}});
*
resistance
value that will be used for all of the
* properties in that particular throwProps
tween like this:
*
* ThrowPropsPlugin.to(mc, {throwProps:{_x:500, _y:-300, resistance:150}});
*
* //-OR-
*
* ThrowPropsPlugin.to(mc, {throwProps:{_x:{velocity:500, max:800, min:0}, _y:{velocity:-300, max:700, min:100}, resistance:150}});
*
resistance
should always be a positive value, although velocity
can be negative.
* resistance
always works against velocity
. If no resistance
value is
* found, the ThrowPropsPlugin.defaultResistance
value will be used. The resistance
* values merely affect the duration of the tween and can be overriden by the maxDuration
and
* minDuration
parameters. Think of the resistance
as more of a suggestion that
* ThrowPropsPlugin uses in its calculations rather than an absolute set-in-stone value. When there are multiple
* properties in one throwProps tween (like x
and y
) and the calculated duration
* for each one is different, the longer duration is always preferred in order to make things animate more
* smoothly.maxDuration
and
* minDuration
parameters for that. You can use the overshootTolerance
* parameter to set a maximum number of seconds that can be added to the tween's duration (if necessary) to
* accommodate temporarily overshooting the end value before smoothly returning to it at the end of the tween.
* This can happen in situations where the initial velocity would normally cause it to exceed the max
* or min
values. An example of this would be in the iOS (iPhone or iPad) when you flick-scroll
* so quickly that the content would shoot past the end of the scroll area. Instead of jerking to a sudden stop
* when it reaches the edge, the content briefly glides past the max/min position and gently eases back into place.
* The larger the overshootTolerance
the more leeway the tween has to temporarily shoot past the
* max/min if necessary.
*
*
* @param target Target object whose properties the tween affects. This can be ANY object, not just a DisplayObject.
* @param vars An object containing the end values of the properties you're tweening, and it must also contain a throwProps
object. For example, to create a tween that tweens mc.x
at an initial velocity of 500 and mc.y
at an initial velocity of -300 and applies a resistance of 80 and uses the Strong.easeOut
easing equation and calls the method tweenCompleteHandler
when it is done, the vars
object would look like: {throwProps:{x:500, y:-300, resistance:80}, ease:Strong.easeOut, onComplete:tweenCompleteHandler}
.
* @param maxDuration Maximum duration of the tween
* @param minDuration Minimum duration of the tween
* @param overshootTolerance sets a maximum number of seconds that can be added to the tween's duration (if necessary) to
* accommodate temporarily overshooting the end value before smoothly returning to it at the end of the tween.
* This can happen in situations where the initial velocity would normally cause it to exceed the max
* or min
values. An example of this would be in the iOS (iPhone or iPad) when you flick-scroll
* so quickly that the content would shoot past the end of the scroll area. Instead of jerking to a sudden stop
* when it reaches the edge, the content briefly glides past the max/min position and gently eases back into place.
* The larger the overshootTolerance
the more leeway the tween has to temporarily shoot past the
* max/min if necessary.
* @return TweenLite instance
*
* @see #defaultResistance
*/
static public function to(target:Object, vars:Object, maxDuration:Number, minDuration:Number, overshootTolerance:Number):TweenLite {
if (vars.throwProps == undefined) {
vars = {throwProps:vars};
}
return new TweenLite(target, calculateTweenDuration(target, vars, maxDuration, minDuration, overshootTolerance), vars);
}
/**
* Determines the amount of change given a particular velocity, an specific easing equation,
* and the duration that the tween will last. This is useful for plotting the resting position
* of an object that starts out at a certain velocity and decelerates based on an ease (like
* Strong.easeOut
).
*
* @param velocity The initial velocity
* @param ease The easing equation (like Strong.easeOut
or Quad.easeOut
).
* @param duration The duration (in seconds) of the tween
* @param checkpoint A value between 0 and 1 (typically 0.05) that is used to measure an easing equation's initial strength. The goal is for the value to have moved at the initial velocity through that point in the ease. So 0.05 represents 5%. If the initial velocity is 500, for example, and the ease is Strong.easeOut
and checkpoint
is 0.05, it will measure 5% into that ease and plot the position that would represent where the value would be if it was moving 500 units per second for the first 5% of the tween. If you notice that your tween appears to start off too fast or too slow, try adjusting the checkpoint
higher or lower slightly. Typically 0.05 works great.
* @return The amount of change (can be positive or negative based on the velocity)
*/
public static function calculateChange(velocity:Number, ease:Function, duration:Number, checkpoint:Number):Number {
if (checkpoint == undefined) {
checkpoint = 0.05;
}
return (duration * checkpoint * velocity) / ease(checkpoint, 0, 1, 1);
}
/**
* Calculates the duration (in seconds) that it would take to move from a particular start value
* to an end value at the given initial velocity, decelerating according to a certain easing
* equation (like Strong.easeOut
).
*
* @param start Starting value
* @param end Ending value
* @param velocity the initial velocity at which the starting value is changing
* @param ease The easing equation used for deceleration (like Strong.easeOut
or Quad.easeOut
).
* @param checkpoint A value between 0 and 1 (typically 0.05) that is used to measure an easing equation's initial strength. The goal is for the value to have moved at the initial velocity through that point in the ease. So 0.05 represents 5%. If the initial velocity is 500, for example, and the ease is Strong.easeOut
and checkpoint
is 0.05, it will measure 5% into that ease and plot the position that would represent where the value would be if it was moving 500 units per second for the first 5% of the tween. If you notice that your tween appears to start off too fast or too slow, try adjusting the checkpoint
higher or lower slightly. Typically 0.05 works great.
* @return The duration (in seconds) that it would take to move from the start value to the end value at the initial velocity provided, decelerating according to the ease.
*/
public static function calculateDuration(start:Number, end:Number, velocity:Number, ease:Function, checkpoint:Number):Number {
if (checkpoint == undefined) {
checkpoint = 0.05;
}
return Math.abs( (end - start) * ease(checkpoint, 0, 1, 1) / velocity / checkpoint );
}
/**
* Analyzes various throwProps variables (like initial velocities, max/min values,
* and resistance) and determines the appropriate duration. Typically it is best to
* use the ThrowPropsPlugin.to()
method for this, but calculateTweenDuration()
* could be convenient if you want to create a TweenMax instance instead of a TweenLite instance
* (which is what throwPropsPlugin.to()
returns).
*
* @param target Target object whose properties the tween affects. This can be ANY object, not just a DisplayObject.
* @param vars An object containing the end values of the properties you're tweening, and it must also contain a throwProps
object. For example, to create a tween that tweens mc.x
at an initial velocity of 500 and mc.y
at an initial velocity of -300 and applies a resistance of 80 and uses the Strong.easeOut
easing equation and calls the method tweenCompleteHandler
when it is done, the vars
object would look like: {throwProps:{x:500, y:-300, resistance:80}, ease:Strong.easeOut, onComplete:tweenCompleteHandler}
.
* @param maxDuration Maximum duration (in seconds)
* @param minDuration Minimum duration (in seconds)
* @param overshootTolerance sets a maximum number of seconds that can be added to the tween's duration (if necessary) to
* accommodate temporarily overshooting the end value before smoothly returning to it at the end of the tween.
* This can happen in situations where the initial velocity would normally cause it to exceed the max
* or min
values. An example of this would be in the iOS (iPhone or iPad) when you flick-scroll
* so quickly that the content would shoot past the end of the scroll area. Instead of jerking to a sudden stop
* when it reaches the edge, the content briefly glides past the max/min position and gently eases back into place.
* The larger the overshootTolerance
the more leeway the tween has to temporarily shoot past the
* max/min if necessary.
* @return The duration (in seconds) that the tween should use.
*/
public static function calculateTweenDuration(target:Object, vars:Object, maxDuration:Number, minDuration:Number, overshootTolerance:Number):Number {
var duration:Number = 0;
var clippedDuration:Number = 9999999999;
var throwPropsVars:Object = (vars.throwProps != undefined) ? vars.throwProps : vars;
var ease:Function = (typeof(vars.ease) == "function") ? vars.ease : _easeOut;
var checkpoint:Number = isNaN(throwPropsVars.checkpoint) ? 0.05 : Number(throwPropsVars.checkpoint);
var resistance:Number = isNaN(throwPropsVars.resistance) ? defaultResistance : Number(throwPropsVars.resistance);
var curProp:Object, curDuration:Number, curVelocity:Number, curResistance:Number, end:Number, curClippedDuration:Number;
for (var p:String in throwPropsVars) {
if (p != "resistance" && p != "checkpoint") {
curProp = throwPropsVars[p];
if (typeof(curProp) == "number") {
curVelocity = Number(curProp);
curDuration = (curVelocity * resistance > 0) ? curVelocity / resistance : curVelocity / -resistance;
} else {
curVelocity = Number(curProp.velocity) || 0;
curResistance = isNaN(curProp.resistance) ? resistance : Number(curProp.resistance);
curDuration = (curVelocity * curResistance > 0) ? curVelocity / curResistance : curVelocity / -curResistance;
end = target[p] + calculateChange(curVelocity, ease, curDuration, checkpoint);
if (curProp.max != undefined && end > Number(curProp.max)) {
//if the value is already exceeding the max or the velocity is too low, the duration can end up being uncomfortably long but in most situations, users want the snapping to occur relatively quickly (0.75 seconds), so we implement a cap here to make things more intuitive.
curClippedDuration = (target[p] > curProp.max || (curVelocity > -15 && curVelocity < 45)) ? 0.75 : calculateDuration(target[p], curProp.max, curVelocity, ease, checkpoint);
if (curClippedDuration + overshootTolerance < clippedDuration) {
clippedDuration = curClippedDuration + overshootTolerance;
}
} else if (curProp.min != undefined && end < Number(curProp.min)) {
//if the value is already exceeding the min or if the velocity is too low, the duration can end up being uncomfortably long but in most situations, users want the snapping to occur relatively quickly (0.75 seconds), so we implement a cap here to make things more intuitive.
curClippedDuration = (target[p] < curProp.min || (curVelocity > -45 && curVelocity < 15)) ? 0.75 : calculateDuration(target[p], curProp.min, curVelocity, ease, checkpoint);
if (curClippedDuration + overshootTolerance < clippedDuration) {
clippedDuration = curClippedDuration + overshootTolerance;
}
}
if (curClippedDuration > duration) {
duration = curClippedDuration;
}
}
if (curDuration > duration) {
duration = curDuration;
}
}
}
if (duration > clippedDuration) {
duration = clippedDuration;
}
if (duration > maxDuration) {
return maxDuration;
} else if (duration < minDuration) {
return minDuration;
}
return duration;
}
/** @private **/
public function onInitTween(target:Object, value:Object, tween:TweenLite):Boolean {
_target = target;
_tween = tween;
_props = [];
var ease:Function = (typeof(_tween.vars.ease) == "function") ? _tween.vars.ease : _easeOut;
var checkpoint:Number = isNaN(value.checkpoint) ? 0.05 : Number(value.checkpoint);
var p:String, curProp:Object, velocity:Number, change1:Number, end:Number, change2:Number, duration:Number = _tween.cachedDuration, cnt:Number = 0;
for (p in value) {
if (p != "resistance" && p != "checkpoint") {
curProp = value[p];
if (typeof(curProp) == "number") {
velocity = Number(curProp);
} else if (!isNaN(curProp.velocity)) {
velocity = Number(curProp.velocity);
} else {
trace("ERROR: No velocity was defined in the throwProps tween of " + target + " property: " + p);
velocity = 0;
}
change1 = calculateChange(velocity, ease, duration, checkpoint);
change2 = 0;
if (typeof(curProp) != "number") {
end = _target[p] + change1;
if (curProp.max != undefined && Number(curProp.max) < end) {
change2 = (curProp.max - _target[p]) - change1;
} else if (curProp.min != undefined && Number(curProp.min) > end) {
change2 = (curProp.min - _target[p]) - change1;
}
}
_props[cnt++] = new ThrowProp(p, Number(target[p]), change1, change2);
this.overwriteProps[cnt] = p;
}
}
return true;
}
/** @private **/
private static function _easeOut(t:Number, b:Number, c:Number, d:Number):Number {
return 1 - (t = 1 - (t / d)) * t;
}
/** @private **/
public function killProps(lookup:Object):Void {
var i:Number = _props.length;
while (i--) {
if (lookup[_props[i].property] != undefined) {
_props.splice(i, 1);
}
}
super.killProps(lookup);
}
/** @private **/
public function set changeFactor(n:Number):Void {
var i:Number = _props.length, curProp:ThrowProp;
if (!this.round) {
while (i--) {
curProp = _props[i];
_target[curProp.property] = curProp.start + curProp.change1 * n + curProp.change2 * n * n;
}
} else {
var val:Number;
while (i--) {
curProp = _props[i];
val = curProp.start + curProp.change1 * n + curProp.change2 * n * n;
_target[curProp.property] = (val >= 0) ? (val + 0.5) >> 0 : (val - 0.5) >> 0; //4 times as fast as Math.round();
}
}
}
}