How to Play a Timeline Backwards (with Easing!)

ActionScript 2.0 ActionScript 3.0

Reader James Colvin wrote me in mid-December to ask if I had any thoughts on playing a timeline backwards.  As it turns out, this question comes up every now and then on the Adobe forums, where longtime regular kglad usually posts his very handy custom function in reply.  In kglad’s version, the MovieClip.nextFrame() and prevFrame() methods are used in cahoots with setInterval() to accomplish the goal.  He often assigns the function to the Object.prototype property of the MovieClip class, which makes the new functionality available to all movie clips (a pre-AS3 technique).

My initial reaction was to search the forums and send back a link, but James’ question had an interesting twist:  could this non-standard timeline movement include easing?  Wow, what a cool challenge!  So I thought about it off and on over the holidays, and a neat solution occurred to me just this morning. 

A handful of answers, not so short, but sweet

What follows is my personal take on a solution, presented in three formats:  two versions in AS2 and one version in AS3.  Why the two versions of the AS2?  Frankly, while both are fairly short, one is even simpler than the other.  The only thing is, it involves an AS2 feature some would argue as inefficient.  (To my thinking, it would only be a problem in complex FLAs, but I’ll supply both and you can take your pick.)  The second AS2 version is close enough to AS3 in syntax that it acts as a good introduction to the new language.

The trick to my approach, in all cases, is to use the native Tween class for the easing, then take note of a numeric property adjusted by the programmatic tween.

ActionScript 2.0, Object.watch() approach

import mx.transitions.Tween;
import mx.transitions.easing.Bounce;

stop();

var w:Object = new Object();
w.prop = 0;
w.watch("prop", onUpdate);

function onUpdate(prop:String, oldVal:Number, newVal:Number):Number {
  gotoAndStop(Math.floor(newVal));
  return newVal;
}

var tween:Tween = new Tween(w, "prop", Bounce.easeIn, 58, 2, 4, true);

So what’s going on?  This particular batch of code may look a bit more cryptic than my usual examples, I realize, but once you see what’s going on, it’s not too bad.

The first two lines import the Tween and Bounce classes.  There are a number of easing classes in AS2, and you’ll find them in the Components Language Reference rather than the ActionScript 2.0 Language Reference (… Regular approximates a standard timeline tween, Strong is stronger than that, Bounce gives you what it says [a bit of bounce!], None gives you a tween without easing, and there are a few more; search “About easing classes and methods”).

Next, a simple stop() halts the timeline.  Keep in mind, this code appears in frame 1 of the main timeline. so James’ animation (and yours) will start on frame 1 as well and extend as far as you need — in this example, the animation (a series of standard timeline tweens) extends through frame 58.

Now it gets a bit interesting.  Check out these three lines again:

var w:Object = new Object();
w.prop = 0;
w.watch("prop", onUpdate);

An arbitrarily named variable, w, is declared and set to an instance of the Object class (to me, “w” stands for “watcher,” but name your variable what you like).  This w object is given a property named prop, which is set to 0.  As we continue, this property will be updated by the Tween class to indicate which frame in the main timeline to travel to.  Finally, the Object.watch() method is invoked on w.  This is the line that some will find inefficient, so keep that in mind as you experiment.  The reason is, Object.watch() causes Flash Player to keep a constant eye on the value of w.prop (or whatever property you feed in), which sets up a tight loop that may monitor the property longer than you need it to.  For the sake of this example — and, I would argue, for many simple implementations — you’re probably fine.  What Object.watch() does is trigger a custom function (provided by you) whenever the given property (here, prop) changes.  Note that the property is provided in quotes, and the function is provided without the parentheses.

And what might this custom function be?  Check it out.  The syntax is a bit weird, but for as useful as Object.watch() is, the weird is worth it.

function onUpdate(prop:String, oldVal:Number, newVal:Number):Number {
  gotoAndStop(Math.floor(newVal));
  return newVal;
}

The function receives three incoming parameters:  prop, oldVal, and newVal.  Here, prop is the property (referred to as a string) that was passed in earlier by w.watch()oldVal is the value of that property — before it gets changed by whatever force changes it (for us, that force is the Tween instance).  newVal is the value after the property gets changed.  Inside the function, a simple gotoAndStop() tells the main timeline to proceed to a frame determined by the prop property (in turn determined by the Tween instance).  The Math.floor() part rounds down the number, in case it’s not an integer.  If you run the tween from, say, 58 to 2, prop will update from 58 downwards to 2.  The exact numbers will change depending on easing, and the timeline will travel to each of those frames (backwards or forwards) in response.  The return statement is needed to ensure that w’s prop property actually updates.

Finally, we come to the tween:

var tween:Tween = new Tween(w, "prop", Bounce.easeIn, 58, 2, 4, true);

An arbitrarily named variable, tween, is declared and set to an instance of the Tween class.  A number of parameters feed the constructor, and here’s what they mean:

  • w is our Object instance;
  • "prop" is w.prop, again indicated as a string;
  • the expression Bounce.easeIn is a static method of the Bounce class (a standard ease out would be Regular.easeOut; again, check the Help docs);
  • 58 is the starting position, frame 58;
  • 2 is the destination position, frame 2;
  • 4 means “do this tween over a period of four seconds”;
  • true means “yes, seconds” (if this one was false, the previous parameter would refer to four frames, rather than seconds — that would be much quicker!)

And that’s it.  The neat thing is, the Tween class gives you a number of useful methods and events, so you can respond to the completion of a tween (for example) by repeating it, or repeating it backwards (and so on):

// An example, using Tween.onMotionFinished
// and Tween.yoyo()
tween.onMotionFinished = function():Void {
  this.yoyo();
}

Why tween from frame 58 down to 2 instead of down to 1?  As soon as the playhead hits frame 1 again, all this ActionScript we’ve been discussing gets triggered all over again.

ActionScript 2.0, custom event approach

If you prefer not to use Object.watch(), here’s another approach.  It requires a small custom class file in addition to the keyframe code, so we’ll look at both.  Note how closely the keyframe code matches the previous version:

stop();
import mx.transitions.Tween;
import mx.transitions.easing.Bounce;

var w:Watcher = new Watcher();
w.addEventListener("update", onUpdate);

function onUpdate(evt:Object):Void {
  gotoAndStop(Math.floor(Number(evt.prop)));
}

var tween:Tween = new Tween(w, "prop", Bounce.easeIn, 58, 2, 4, true);

Instead of the Object.watch() portion — in fact, instead of the Object instance at all — we now have an instance of a custom Watcher class.  This instance, w (same variable name as before, for convenience) features a Watcher.update event, which is handled to perform a custom onUpdate() function.  This happens via the addEventListener() method.  The onUpdate() function again tells the main timeline to gotoAndStop(), only this time there’s only a single parameter, evt.  The evt parameter carries with it a property named prop, and that property tells the timeline which frame to visit.  Same concept, different approach.

Here’s the code for the Watcher class.  This goes in a text file external to the FLA.

import mx.events.EventDispatcher;
class Watcher {
  private var _prop:Object;
  public var addEventListener:Function;
  public var removeEventListener:Function;
  private var dispatchEvent:Function;
  public function Watcher() {
    EventDispatcher.initialize(this);
  }
  public function get prop():Object {
    return _prop;
  }
  public function set prop(o:Object):Void {
    _prop = o;
    dispatchEvent({type:"update", prop:o});
  }
}

The native EventDispatcher class is imported, so that the Watcher class can dispatch events.  The class is declared, and a handful of properties are declared:  _prop (should be familiar by now) and three functions, which are provided by the EventDispatcher.initialize() method just inside the constructor.  (See “How to Raise Custom Events (EventDispatcher)” for details on this procedure in AS2.)

A pair of getter/setter functions allow _prop to be changed from outside the class (in this case, by the Tween instance), and note that the set function dispatches the event, of type “update” and with a property prop whose value is determined by the incoming value (again, by the tween).  In the keyframe code that uses addEventListener(), the gotoAndStop() line that uses evt.prop gets its value from prop as seen here.

ActionScript 3.0

AS3 no longer supports Object.watch(), so the only possibility for an alternate approach involves a custom event.  This version’s keyframe portion matches the AS2 version almost 100%.  (See Justin Everett-Church’s blog for reasons why AS3 crossed this feature of its list.)  Here’s the code:

import fl.transitions.Tween;
import fl.transitions.easing.Bounce;

stop();

var w:Watcher = new Watcher();
w.addEventListener(WatcherEvent.UPDATE, onUpdate);

function onUpdate(evt:WatcherEvent):void {
  gotoAndStop(Math.floor(evt.prop));
}

var tween:Tween = new Tween(w, "prop", Bounce.easeIn, 58, 2, 4, true);

The import statements change just a tad (the package is now fl.transitions, instead of mx.transitions).  The addEventListener() line uses a static WatcherEvent constant instead of the string “update”.  The onUpdate() function only changes in that its incoming parameter is of type WatcherEvent, rather than Object.  What this should tell you is that we need two custom classes for the AS3 version:  Watcher and WatcherEvent.  Here they are:

package {
  import WatcherEvent;
  import flash.events.EventDispatcher;
  public class Watcher extends EventDispatcher {
    private var _prop:Object;
    public function Watcher() {};
    public function get prop():Object {
      return _prop;
    }
    public function set prop(o:Object):void {
      _prop = o;
      dispatchEvent(new WatcherEvent(WatcherEvent.UPDATE, o));
    }
  }
}

Here, the class actually extends EventDispatcher, rather than using it as a mix-in.  The syntax changes a bit, but the concept is still the same.  Here, the constructor is empty.  This time, the set function dispatches a new instance of WatcherEvent, along with the updated value, o.

package {
  import flash.events.Event;
  public class WatcherEvent extends Event {
    public static const UPDATE:String = "update";
    private var _prop:Object = new Object();
    public function WatcherEvent(type:String, prop:Object) {
      super(type);
      _prop = prop;
    }
    public function get prop():Object {
      return _prop;
    }
    public override function clone():Event {
      return new WatcherEvent(type, _prop);
    }
  }
}

WatcherEvent extends the Event class, then provides the custom event, update, which is stored as a static constant (UPDATE) of this class.  See Trevor McCauley’s “Introduction to event handling in ActionScript 3.0” for an in-depth look at raising custom events in AS3.

Leave a Reply