/** * VERSION: 1.698 * DATE: 2012-03-29 * AS2 (AS3 version is also available) * UPDATES AND DOCS AT: http://www.greensock.com/timelinemax/ **/ import com.greensock.*; import com.greensock.core.*; /** * TimelineMax extends TimelineLite, offering exactly the same functionality plus useful * (but non-essential) features like repeat, repeatDelay, yoyo, * currentLabel, addCallback(), removeCallback(), tweenTo(), getLabelAfter(), getLabelBefore(), * and getActive() (and probably more in the future). It is the ultimate sequencing tool. * Think of a TimelineMax instance like a virtual MovieClip timeline or a container where * you place tweens (or other timelines) over the course of time. You can: * *
currentProgress
property. For example, to skip to
* the halfway point, set myTimeline.currentProgress = 0.5
.currentTime
, or currentProgress
property to fastforward/rewind the timeline. You could
* even attach a slider to one of these properties to give the user the ability to drag
* forwards/backwards through the whole timeline.vars
object.
*
* import com.greensock.TweenLite;
* import com.greensock.TimelineMax;
*
* //create the timeline
* var myTimeline:TimelineMax = new TimelineMax();
*
* //add a tween
* myTimeline.append(new TweenLite(mc, 1, {_x:200, _y:100}));
*
* //add another tween at the end of the timeline (makes sequencing easy)
* myTimeline.append(new TweenLite(mc, 0.5, {_alpha:0}));
*
* //repeat the whole timeline twice.
* myTimeline.repeat = 2;
*
* //delay the repeat by 0.5 seconds each time.
* myTimeline.repeatDelay = 0.5;
*
* //stop/pause the timeline.
* myTimeline.stop();
*
* //reverse it anytime...
* myTimeline.reverse();
*
* //Add a "spin" label 3-seconds into the timeline.
* myTimeline.addLabel("spin", 3);
*
* //insert a rotation tween at the "spin" label (you could also define the insert point as the time instead of a label)
* myTimeline.insert(new TweenLite(mc, 2, {_rotation:"360"}), "spin");
*
* //go to the "spin" label and play the timeline from there...
* myTimeline.gotoAndPlay("spin");
*
* //call myFunction when the "virtual playhead" travels past the 1.5-second point.
* myTimeline.addCallback(myFunction, 1.5);
*
* //add a tween to the beginning of the timeline, pushing all the other existing tweens back in time
* myTimeline.prepend(new TweenLite(mc, 1, {tint:0xFF0000}));
*
* //nest another TimelineMax inside your timeline...
* var nestedTimeline:TimelineMax = new TimelineMax();
* nestedTimeline.append(new TweenLite(mc2, 1, {_x:200}));
* myTimeline.append(nestedTimeline);
*
*
* insertMultiple() provides some very powerful sequencing tools as well, allowing you to add an Array of
* tweens/timelines and optionally align them with SEQUENCE or START modes, and even stagger them if you want. For example, to insert
* 3 tweens into the timeline, aligning their start times but staggering them by 0.2 seconds,
*
* myTimeline.insertMultiple([new TweenLite(mc, 1, {_y:"100"}),
* new TweenLite(mc2, 1, {_y:"120"}),
* new TweenLite(mc3, 1, {_y:"140"})],
* 0,
* TweenAlign.START,
* 0.2);
*
* var myTimeline:TimelineMax = new TimelineMax({tweens:[new TweenLite(mc1, 1, {_y:"100"}), TweenMax.to(mc2, 1, {tint:0xFF0000})], align:TweenAlign.SEQUENCE, onComplete:myFunction, repeat:2, repeatDelay:1});
new TimelineMax({paused:true, onComplete:myFunction, repeat:2, yoyo:true})
*
* currentProgress
property to 1 immediately
* after creating the timeline.currentProgress
won't necessarily
* be zero when onStart is called. For example, if the timeline is created and then its currentProgress
* property is immediately set to 0.5 or if its currentTime
property is set to something other than zero,
* onStart will still get fired because it is the first time the timeline is getting rendered.)currentProgress
value to a lower value, etc.). It can, however, improve speed and memory
* management. TweenLite's root timelines use autoRemoveChildren:true.
reversed
property though). So if repeat is
* 2 and yoyo is false, it will look like: start - 1 - 2 - 3 - 1 - 2 - 3 - 1 - 2 - 3 - end. But
* if repeat is 2 and yoyo is true, it will look like: start - 1 - 2 - 3 - 3 - 2 - 1 - 1 - 2 - 3 - end.
*
* myTimeline.tweenTo("myLabel2");
*
* If you want advanced control over the tween, like adding an onComplete or changing the ease or adding a delay,
* just pass in a vars object with the appropriate properties. For example, to tween to the 5-second point on the
* timeline and then call a function named myFunction
and pass in a parameter that's references this
* TimelineMax and use a Strong.easeOut ease, you'd do:
*
* myTimeline.tweenTo(5, {onComplete:myFunction, onCompleteParams:[myTimeline], ease:Strong.easeOut});
*
* Remember, this method simply creates a TweenLite instance that tweens the currentTime
property of your timeline.
* So you can store a reference to that tween if you want, and you can kill() it anytime. Also note that tweenTo()
* does NOT affect the timeline's reversed
property. So if your timeline is oriented normally
* (not reversed) and you tween to a time/label that precedes the current time, it will appear to go backwards
* but the reversed
property will not change to true
. Also note that tweenTo()
* pauses the timeline immediately before tweening its currentTime
property, and it stays paused after the tween completes.
* If you need to resume playback, you could always use an onComplete to call the resume()
method.tweenFromTo()
so that you can define the starting point and ending point, allowing the
* duration to be accurately determined immediately.
*
* @see #tweenFromTo()
* @param timeOrLabel The destination time in seconds (or frame if the timeline is frames-based) or label to which the timeline should play. For example, myTimeline.tweenTo(5) would play from wherever the timeline is currently to the 5-second point whereas myTimeline.tweenTo("myLabel") would play to wherever "myLabel" is on the timeline.
* @param vars An optional vars object that will be passed to the TweenLite instance. This allows you to define an onComplete, ease, delay, or any other TweenLite special property. onInit is the only special property that is not available (tweenTo() sets it internally)
* @return TweenLite instance that handles tweening the timeline to the desired time/label.
*/
public function tweenTo(timeOrLabel:Object, vars:Object):TweenLite {
var varsCopy:Object = {ease:easeNone, overwrite:2, useFrames:this.useFrames, immediateRender:false};
for (var p:String in vars) {
varsCopy[p] = vars[p];
}
varsCopy.onInit = onInitTweenTo;
varsCopy.onInitParams = [null, this, NaN];
varsCopy.currentTime = parseTimeOrLabel(timeOrLabel);
var tl:TweenLite = new TweenLite(this, (Math.abs(Number(varsCopy.currentTime) - this.cachedTime) / this.cachedTimeScale) || 0.001, varsCopy);
tl.vars.onInitParams[0] = tl;
return tl;
}
/**
* Creates a linear tween that essentially scrubs the playhead from a particular time or label to another
* time or label and then stops. If you plan to sequence multiple playhead tweens one-after-the-other,
* tweenFromTo()
is better to use than tweenTo()
because it allows the duration
* to be determined immediately, ensuring that subsequent tweens that are appended to a sequence are
* positioned appropriately. For example, to make the TimelineMax play from the label "myLabel1" to the "myLabel2"
* label, and then from "myLabel2" back to the beginning (a time of 0), simply do:
*
* var playheadTweens:TimelineMax = new TimelineMax();
* playheadTweens.append( myTimeline.tweenFromTo("myLabel1", "myLabel2") );
* playheadTweens.append( myTimeline.tweenFromTo("myLabel2", 0);
*
* If you want advanced control over the tween, like adding an onComplete or changing the ease or adding a delay,
* just pass in a vars object with the appropriate properties. For example, to tween from the start (0) to the
* 5-second point on the timeline and then call a function named myFunction
and pass in a parameter
* that's references this TimelineMax and use a Strong.easeOut ease, you'd do:
*
* myTimeline.tweenFromTo(0, 5, {onComplete:myFunction, onCompleteParams:[myTimeline], ease:Strong.easeOut});
*
* Remember, this method simply creates a TweenLite instance that tweens the currentTime
property of your timeline.
* So you can store a reference to that tween if you want, and you can kill()
it anytime. Also note that tweenFromTo()
* does NOT affect the timeline's reversed
property. So if your timeline is oriented normally
* (not reversed) and you tween to a time/label that precedes the current time, it will appear to go backwards
* but the reversed
property will not change to true
. Also note that tweenFromTo()
* pauses the timeline immediately before tweening its currentTime
property, and it stays paused after the tween completes.
* If you need to resume playback, you could always use an onComplete to call the resume()
method.
*
* @see #tweenTo()
* @param fromTimeOrLabel The beginning time in seconds (or frame if the timeline is frames-based) or label from which the timeline should play. For example, myTimeline.tweenTo(0, 5)
would play from 0 (the beginning) to the 5-second point whereas myTimeline.tweenFromTo("myLabel1", "myLabel2")
would play from "myLabel1" to "myLabel2".
* @param toTimeOrLabel The destination time in seconds (or frame if the timeline is frames-based) or label to which the timeline should play. For example, myTimeline.tweenTo(0, 5)
would play from 0 (the beginning) to the 5-second point whereas myTimeline.tweenFromTo("myLabel1", "myLabel2")
would play from "myLabel1" to "myLabel2".
* @param vars An optional vars object that will be passed to the TweenLite instance. This allows you to define an onComplete, ease, delay, or any other TweenLite special property. onInit is the only special property that is not available (tweenFromTo()
sets it internally)
* @return TweenLite instance that handles tweening the timeline between the desired times/labels.
*/
public function tweenFromTo(fromTimeOrLabel:Object, toTimeOrLabel:Object, vars:Object):TweenLite {
var tl:TweenLite = tweenTo(toTimeOrLabel, vars);
tl.vars.onInitParams[2] = parseTimeOrLabel(fromTimeOrLabel);
tl.duration = Math.abs(Number(tl.vars.currentTime) - tl.vars.onInitParams[2]) / this.cachedTimeScale;
return tl;
}
/** @private **/
private static function onInitTweenTo(tween:TweenLite, timeline:TimelineMax, fromTime:Number):Void {
timeline.paused = true;
if (!isNaN(fromTime)) {
timeline.currentTime = fromTime;
}
if (tween.vars.currentTime != timeline.currentTime) { //don't make the duration zero - if it's supposed to be zero, don't worry because it's already initting the tween and will complete immediately, effectively making the duration zero anyway. If we make duration zero, the tween won't run at all.
tween.duration = Math.abs(Number(tween.vars.currentTime) - timeline.currentTime) / timeline.cachedTimeScale;
}
}
/** @private **/
private static function easeNone(t:Number, b:Number, c:Number, d:Number):Number {
return t / d;
}
/** @inheritDoc **/
public function renderTime(time:Number, suppressEvents:Boolean, force:Boolean):Void {
if (this.gc) {
this.setEnabled(true, false);
} else if (!this.active && !this.cachedPaused) {
this.active = true;
}
var totalDur:Number = (this.cacheIsDirty) ? this.totalDuration : this.cachedTotalDuration, prevTime:Number = this.cachedTime, prevTotalTime:Number = this.cachedTotalTime, prevStart:Number = this.cachedStartTime, prevTimeScale:Number = this.cachedTimeScale, tween:TweenCore, isComplete:Boolean, rendered:Boolean, repeated:Boolean, next:TweenCore, dur:Number, prevPaused:Boolean = this.cachedPaused;
if (time >= totalDur) {
if ((prevTotalTime != totalDur || this.cachedDuration == 0) && _rawPrevTime != time) {
this.cachedTotalTime = totalDur;
if (!this.cachedReversed && this.yoyo && _repeat % 2 != 0) {
this.cachedTime = 0;
forceChildrenToBeginning(0, suppressEvents);
} else {
this.cachedTime = this.cachedDuration;
forceChildrenToEnd(this.cachedDuration, suppressEvents);
}
isComplete = !this.hasPausedChild() && !this.cachedReversed;
rendered = true;
if (this.cachedDuration == 0 && isComplete && (time == 0 || _rawPrevTime < 0)) { //In order to accommodate zero-duration timelines, we must discern the momentum/direction of time in order to render values properly when the "playhead" goes past 0 in the forward direction or lands directly on it, and also when it moves past it in the backward direction (from a postitive time to a negative time).
force = true;
}
}
} else if (time <= 0) {
if (time < 0) {
this.active = false;
if (this.cachedDuration == 0 && _rawPrevTime > 0) { //In order to accommodate zero-duration timelines, we must discern the momentum/direction of time in order to render values properly when the "playhead" goes past 0 in the forward direction or lands directly on it, and also when it moves past it in the backward direction (from a postitive time to a negative time).
force = true;
isComplete = true;
}
} else if (time == 0 && !this.initted) {
force = true;
}
if (prevTotalTime != 0 && _rawPrevTime != time) {
this.cachedTotalTime = 0;
this.cachedTime = 0;
forceChildrenToBeginning(0, suppressEvents);
rendered = true;
if (this.cachedReversed) {
isComplete = true;
}
}
} else {
this.cachedTotalTime = this.cachedTime = time;
}
_rawPrevTime = time;
if (_repeat != 0) {
var prevCycles:Number = _cyclesComplete;
var cycleDuration:Number = this.cachedDuration + _repeatDelay;
var prevCycles:Number = _cyclesComplete;
if ((_cyclesComplete = (this.cachedTotalTime / cycleDuration) >> 0) == (this.cachedTotalTime / cycleDuration) && _cyclesComplete != 0) {
_cyclesComplete--; //otherwise when rendered exactly at the end time, it will act as though it is repeating (at the beginning)
}
repeated = Boolean(prevCycles != _cyclesComplete);
if (isComplete) {
if (this.yoyo && _repeat % 2) {
this.cachedTime = 0;
}
} else if (time > 0) {
this.cachedTime = this.cachedTotalTime - (_cyclesComplete * cycleDuration); //originally this.cachedTotalTime % cycleDuration but floating point errors caused problems, so I normalized it. (4 % 0.8 should be 0 but Flash reports it as 0.79999999!)
if (this.yoyo && _cyclesComplete % 2) {
this.cachedTime = this.cachedDuration - this.cachedTime;
} else if (this.cachedTime >= this.cachedDuration) {
this.cachedTime = this.cachedDuration;
}
if (this.cachedTime < 0) {
this.cachedTime = 0;
}
} else {
_cyclesComplete = 0;
}
if (repeated && !isComplete && (this.cachedTotalTime != prevTotalTime || force)) {
/*
make sure children at the end/beginning of the timeline are rendered properly. If, for example,
a 3-second long timeline rendered at 2.9 seconds previously, and now renders at 3.2 seconds (which
would get transated to 2.8 seconds if the timeline yoyos or 0.2 seconds if it just repeats), there
could be a callback or a short tween that's at 2.95 or 3 seconds in which wouldn't render. So
we need to push the timeline to the end (and/or beginning depending on its yoyo value).
*/
var forward:Boolean = Boolean(!this.yoyo || (_cyclesComplete % 2 == 0));
var prevForward:Boolean = Boolean(!this.yoyo || (prevCycles % 2 == 0));
var wrap:Boolean = Boolean(forward == prevForward);
if (prevCycles > _cyclesComplete) {
prevForward = !prevForward;
}
if (prevForward) {
prevTime = forceChildrenToEnd(this.cachedDuration, suppressEvents);
if (wrap) {
prevTime = forceChildrenToBeginning(0, true);
}
} else {
prevTime = forceChildrenToBeginning(0, suppressEvents);
if (wrap) {
prevTime = forceChildrenToEnd(this.cachedDuration, true);
}
}
rendered = false;
}
}
if (this.cachedTime == prevTime && force != true) {
return;
} else if (!this.initted) {
this.initted = true;
}
if (prevTotalTime == 0 && this.vars.onStart && this.cachedTotalTime != 0 && !suppressEvents) {
this.vars.onStart.apply(this.vars.onStartScope, this.vars.onStartParams);
}
if (rendered) {
//already rendered, so ignore
} else if (this.cachedTime > prevTime) {
tween = _firstChild;
while (tween) {
next = tween.nextNode; //record it here because the value could change after rendering...
if (this.cachedPaused && !prevPaused) { //in case a tween pauses the timeline when rendering
break;
} else if (tween.active || (!tween.cachedPaused && tween.cachedStartTime <= this.cachedTime && !tween.gc)) {
if (!tween.cachedReversed) {
tween.renderTime((this.cachedTime - tween.cachedStartTime) * tween.cachedTimeScale, suppressEvents, false);
} else {
dur = (tween.cacheIsDirty) ? tween.totalDuration : tween.cachedTotalDuration;
tween.renderTime(dur - ((this.cachedTime - tween.cachedStartTime) * tween.cachedTimeScale), suppressEvents, false);
}
}
tween = next;
}
} else {
tween = _lastChild;
while (tween) {
next = tween.prevNode; //record it here because the value could change after rendering...
if (this.cachedPaused && !prevPaused) { //in case a tween pauses the timeline when rendering
break;
} else if (tween.active || (!tween.cachedPaused && tween.cachedStartTime <= prevTime && !tween.gc)) {
if (!tween.cachedReversed) {
tween.renderTime((this.cachedTime - tween.cachedStartTime) * tween.cachedTimeScale, suppressEvents, false);
} else {
dur = (tween.cacheIsDirty) ? tween.totalDuration : tween.cachedTotalDuration;
tween.renderTime(dur - ((this.cachedTime - tween.cachedStartTime) * tween.cachedTimeScale), suppressEvents, false);
}
}
tween = next;
}
}
if (_hasUpdate && suppressEvents != true) {
this.vars.onUpdate.apply(this.vars.onUpdateScope, this.vars.onUpdateParams);
}
if (repeated && suppressEvents != true) {
if (this.vars.onRepeat) {
this.vars.onRepeat.apply(this.vars.onRepeatScope, this.vars.onRepeatParams);
}
}
if (isComplete && (prevStart == this.cachedStartTime || prevTimeScale != this.cachedTimeScale) && (totalDur >= this.totalDuration || this.cachedTime == 0)) { //if one of the tweens that was rendered altered this timeline's startTime (like if an onComplete reversed the timeline) or if it added more tweens to the timeline, we shouldn't run complete() because it probably isn't complete. If it is, don't worry, because whatever call altered the startTime would have called complete() if it was necessary at the new time. The only exception is the timeScale property.
complete(true, suppressEvents);
}
}
/**
* Returns the tweens/timelines that are currently active in the timeline.
*
* @param nested determines whether or not tweens and/or timelines that are inside nested timelines should be returned. If you only want the "top level" tweens/timelines, set this to false.
* @param tweens determines whether or not tweens (TweenLite and TweenMax instances) should be included in the results
* @param timelines determines whether or not timelines (TimelineLite and TimelineMax instances) should be included in the results
* @return an Array of active tweens/timelines
*/
public function getActive(nested:Boolean, tweens:Boolean, timelines:Boolean):Array {
if (nested == undefined) {
nested = true;
}
if (tweens == undefined) {
tweens = true;
}
if (timelines == undefined) {
timelines = false;
}
var a:Array = [], all:Array = getChildren(nested, tweens, timelines), cnt:Number = 0, i:Number, tween:TweenCore;
var l:Number = all.length;
for (i = 0; i < l; i++) {
tween = all[i];
//note: we cannot just check tween.active because timelines that contain paused children will continue to have "active" set to true even after the playhead passes their end point (technically a timeline can only be considered complete after all of its children have completed too, but paused tweens are...well...just waiting and until they're unpaused we don't know where their end point will be).
if (!tween.cachedPaused && tween.timeline.cachedTotalTime >= tween.cachedStartTime && tween.timeline.cachedTotalTime < tween.cachedStartTime + tween.cachedTotalDuration / tween.cachedTimeScale && !OverwriteManager.getGlobalPaused(tween.timeline)) {
a[cnt++] = all[i];
}
}
return a;
}
/**
* Returns the next label (if any) that occurs AFTER the time parameter. It makes no difference
* if the timeline is reversed. A label that is positioned exactly at the same time as the time
* parameter will be ignored.
*
* @param time Time after which the label is searched for. If you do not pass a time in, the currentTime will be used.
* @return Name of the label that is after the time passed to getLabelAfter()
*/
public function getLabelAfter(time:Number):String {
if (time == undefined) {
time = this.cachedTime;
}
var labels:Array = getLabelsArray();
var l:Number = labels.length;
for (var i:Number = 0; i < l; i++) {
if (labels[i].time > time) {
return labels[i].name;
}
}
return null;
}
/**
* Returns the previous label (if any) that occurs BEFORE the time parameter. It makes no difference
* if the timeline is reversed. A label that is positioned exactly at the same time as the time
* parameter will be ignored.
*
* @param time Time before which the label is searched for. If you do not pass a time in, the currentTime will be used.
* @return Name of the label that is before the time passed to getLabelBefore()
*/
public function getLabelBefore(time:Number):String {
if (time == undefined) {
time = this.cachedTime;
}
var labels:Array = getLabelsArray();
var i:Number = labels.length;
while (--i > -1) {
if (labels[i].time < time) {
return labels[i].name;
}
}
return null;
}
/** @private Returns an Array of label objects, each with a "time" and "name" property, in the order that they occur in the timeline. **/
private function getLabelsArray():Array {
var a:Array = [];
for (var p:String in _labels) {
a[a.length] = {time:_labels[p], name:p};
}
a.sortOn("time", Array.NUMERIC);
return a;
}
//---- GETTERS / SETTERS -------------------------------------------------------------------------------------------------------
/** @inheritDoc **/
public function set currentProgress(n:Number):Void {
this.currentTime = this.duration * n;
}
/**
* Value between 0 and 1 indicating the progress of the timeline according to its totalDuration
* where 0 is at the beginning, 0.5 is halfway finished, and 1 is finished. currentProgress
,
* by contrast, describes the progress according to the timeline's duration which does not
* include repeats and repeatDelays. For example, if a TimelineMax instance is set
* to repeat once, at the end of the first cycle totalProgress
would only be 0.5
* whereas currentProgress
would be 1. If you tracked both properties over the course of the
* tween, you'd see currentProgress
go from 0 to 1 twice (once for each cycle) in the same
* time it takes the totalProgress
property to go from 0 to 1 once.
**/
public function get totalProgress():Number {
return this.cachedTotalTime / this.totalDuration;
}
public function set totalProgress(n:Number):Void {
setTotalTime(this.totalDuration * n, false);
}
/**
* Duration of the timeline in seconds (or frames for frames-based timelines) including any repeats
* or repeatDelays. "duration", by contrast, does NOT include repeats and repeatDelays.
**/
public function get totalDuration():Number {
if (this.cacheIsDirty) {
var temp:Number = super.totalDuration; //just forces refresh
//Instead of Infinity, we use 999999999999 so that we can accommodate reverses.
this.cachedTotalDuration = (_repeat == -1) ? 999999999999 : this.cachedDuration * (_repeat + 1) + (_repeatDelay * _repeat);
}
return this.cachedTotalDuration;
}
/** @inheritDoc **/
public function get currentTime():Number {
return this.cachedTime;
}
public function set currentTime(n:Number):Void {
if (_cyclesComplete == 0) {
setTotalTime(n, false);
} else if (this.yoyo && (_cyclesComplete % 2 == 1)) {
setTotalTime((this.duration - n) + (_cyclesComplete * (this.cachedDuration + _repeatDelay)), false);
} else {
setTotalTime(n + (_cyclesComplete * (this.duration + _repeatDelay)), false);
}
}
/** Number of times that the timeline should repeat; -1 repeats indefinitely. **/
public function get repeat():Number {
return _repeat;
}
public function set repeat(n:Number):Void {
_repeat = n;
setDirtyCache(true);
}
/** Amount of time in seconds (or frames for frames-based timelines) between repeats **/
public function get repeatDelay():Number {
return _repeatDelay;
}
public function set repeatDelay(n:Number):Void {
_repeatDelay = n;
setDirtyCache(true);
}
/** The closest label that is at or before the current time. **/
public function get currentLabel():String {
return getLabelBefore(this.cachedTime + 0.00000001);
}
}