DailyJS

Let's Make a Framework: Animations Part 6

Alex R. Young

Subscribe

@dailyjs

Facebook

Google+

tutorials frameworks animation lmaf

Let's Make a Framework: Animations Part 6

Posted by Alex R. Young on .
Featured

tutorials frameworks animation lmaf

Let's Make a Framework: Animations Part 6

Posted by Alex R. Young on .

Welcome to part 20 of Let's Make a Framework, the ongoing series about
building a JavaScript framework. This part continues looking at
JavaScript animations.

If you haven't been following along, these articles are tagged with
lmaf. The project we're creating is called Turing and is available on GitHub:
turing.js.

Movement Helper

Continuing on from last week with helpers, I would like to be able to do
this:

turing.anim.move(element, duration, { x: '100px', y: '100px' });

And the element should move 100 pixels across and down from its current
position. The API we've built over the last few weeks will do this for
relative positioned elements with the following code:

anim.animate(element, duration, { 'left': options.x, 'top': options.y });

Using an absolute positioned element will cause it to jump to the first
point in the animation. That means we need to move absolute elements
relative to their original location. I'll come back to this later.

Chained API

Ideally animation calls, whether they be from helpers or
animate, should work when chained. Chaining calls should
create a sequence of animations:

turing.anim.chain(element)
  .highlight()
  .move(1000, { x: '100px', y: '100px' })
  .animate(2000, { height: '0px' });

It would be useful to be able to pause animations, too:

turing.anim.chain(element)
  .highlight()
  .pause(2000)
  .move(1000, { x: '100px', y: '100px' })
  .animate(2000, { width: '1000px' })
  .fadeOut(2000)
  .pause(2000)
  .fadeIn(2000)
  .animate(2000, { width: '20px' })

I've used as similar technique to the Enumerable library's Chainer
class
(part 5). This
isn't reused here but it could be made more generic.

The basic principle is to wrap each chainable method in a function that
can correctly sequence the animation calls. The anim object
contains all of the relevant methods; this object can be iterated over:

for (methodName in anim) {
  (function(methodName) {
    var method = anim[methodName];
    // ...    
  })(methodName);
}

The anonymous function causes the methodName variable to
get bound to the scope. The Chainer class needs to keep
track of the element we're animating, and the current position in time:

Chainer = function(element) {
  this.element = element;
  this.position = 0;
};

Then as the animation progresses, we need to update the position:

for (methodName in anim) {
  (function(methodName) {
    var method = anim[methodName];
    Chainer.prototype[methodName] = function() {
      this.position += current position;
    };
  })(methodName);
}

setTimeout can be used to schedule the animations:

setTimeout(function() {
  method.apply(null, args);
}, this.position);

This will not block, so all of the chained animations will be started at
around the same time. Since position is incremented with
each animation's duration, the animations should fire at approximately
the right time.

The only other thing left to do is progress the arguments
passed to the function to insert the element.

Putting it Together

Chainer = function(element) {
  this.element = element;
  this.position = 0;
};

for (methodName in anim) {
  (function(methodName) {
    var method = anim[methodName];
    Chainer.prototype[methodName] = function() {
      var args = Array.prototype.slice.call(arguments);
      args.unshift(this.element);
      this.position += args[1] || 0;
      setTimeout(function() {
        method.apply(null, args);
      }, this.position);
      return this;
    };
  })(methodName);
}

anim.chain = function(element) {
  return new Chainer(element);
};

Conclusion

Now chaining has been added it's apparent that the animation framework
has some warts: in particular, movement animations will start from start
positions. I'll look at fixing these issues over the coming weeks.