Search This Blog

Thursday, September 30, 2010

Simple Class-Based JavaScript Fade Animation: An Object-Oriented Reworking

Simple Object-Oriented Fade Animation

Normally, I'd just use MooTools for fade animations, but a long-time client wanted a simple, one-time popup that notified her patients that her doctor's office had moved—a temporary courtesy to be removed in a few months.

To make the custom popup just a little nicer, I wanted a simple fade effect without the extra framework code. Of course, I could have just copied and pasted the methods in MooTools, but I was feeling creative and shopped around to see if any native JS solutions existed.

I found a solution I liked, though it lacks the greater flexibility of a more object-oriented approach. It also sets foreign attributes, FadeState and FadeTimeLeft, directly on the element
<div id="fadeEl" FadeState="-2" FadeTimeLeft="33">
    I am the fade element.
</div>
and passes a spliced-together variable that looks up the same element over and over again: document.getElementById(eid),
setTimeout("animateFade(" + new Date().getTime() + ",'" + eid + "')", 33);
neither of which are optimal.

The logic was there, however, and it wasn't hard to convert into an object. The nice thing about objects is that an object is just that: A flexible model of something that once modeled can be easily reused. So, I revamped the function provided there to make it less intrusive and more modular.

I'd originally posted the following re-working in the comments on that site, but this is a slightly more polished example.

Basic Object

The AnimateFade Class

// declare class so we can use the "new" keyword to create unique instances
var AnimateFade = function(elmOrElmId, duration, state) {
    this.setOptions(elmOrElmId, duration, state);
    this.animate();
};

// prototype its methods
AnimateFade.prototype = {

    setOptions: function(elmOrElmId, duration, state) {
        this.fadeDuration = duration;
        this.element = (typeof elmOrElmId == 'string') ? document.getElementById(elmOrElmId) : elmOrElmId;
        this.fadeState = (state && state == 1) ? 1 : 0;
        this.fadeTimeLeft = duration;
        this.lastTick = this.newTick();
    },

    newTick: function() {
        return (new Date).getTime();
    },

    getElapsedTicks: function() {
        return this.currentTick - this.lastTick;
    },

    animate: function() {

        this.currentTick = this.newTick();

        var self = this;
        var elapsedTicks = this.getElapsedTicks();

        // helper function, called in the timeout below,
        // recurses with correct reference to "this"
        var helper = function() {
            self.animate();
        };

        if (this.fadeTimeLeft <= elapsedTicks) {
            this.element.style.opacity = this.fadeState;
            this.element.style.filter = 'alpha(opacity = ' + (this.fadeState * 100) + ')';
            return;
        }

        this.fadeTimeLeft -= elapsedTicks;

        var newOptVal = this.fadeTimeLeft / this.fadeDuration;

        newOptVal = (this.fadeState == 1) ? (1 - newOptVal) : newOptVal;


        this.element.style.opacity = newOptVal;
        this.element.style.filter = 'alpha(opacity = ' + (newOptVal * 100) + ')';

        this.lastTick = this.currentTick;
        this.currentTick = this.newTick();

        setTimeout(helper, 24);
    }
};

// initializer function
var init = function(elmsOrElmIdArr, duration, state) {
    for (var i = 0, l = elmsOrElmIdArr.length; i < l; i++) {
        // "new" keyword creates unique instance
        new AnimateFade(elmsOrElmIdArr[i], duration, state);
    }
};

Instantiations

Single/Multiple Elements, Single Duration and Fade State

To instantiate with one or more elements that will all fade in or out for the same duration:
// pass in either string id or element reference

// Example A: fade just one element
var myOnlyElement = ['myUniqueId'];

// Example B: fade multiple elements
var myMultElms = ['myId', document.getElementsByTagName('div')[7], 'anotherId'];

// how long fade should last
var myDuration = 1000;//1,000 milliseconds, or 1 second

// start fading out
init(myMultElms, myDuration);
// or fading in
init(myMultElms, myDuration, 1);

Multiple Elements, Durations, and Fade States

Or retool the initializer function...
// initializer function capitalizing on parallel arrays
var init = function(elmsOrElmIdArr, duration, state) {
    for (var i = 0, l = elmsOrElmIdArr.length; i < l; i++) {
        // "new" keyword creates unique instance
        new AnimateFade(elmsOrElmIdArr[i], duration[i], state[i]);
    }
};
...to take full advantage of arrays, simultaneously fading multiple elements in and out for different lengths of time:
// set parallel arrays
var myElms = [elm1, elm2, elm3];
var myDurations = [1000, 2500, 300];
var myStates = [0, 1, 0];

// start effects
init(myElms, myDurations, myStates);

// elm1 fades out in 1 second
// elm2 fades in in 2-and-a-half seconds
// elm3 fades out in 3/10ths of a second
This could be useful if you wanted to fade in one set of elements while simultaneously fading out another set, perhaps a confirmation dialog and its acknowledge that the user's choice has either been implemented or canceled. Or whatever.