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.

Friday, August 06, 2010

Make Caching Less Aggressive in Firefox on Development Machines

I have been having some issues with Firefox caching files too aggressively on my development machine at work: not good when you write client-side code.

My first attempt to resolve the issue was to disable caching entirely using the Web Developer toolbar (Disable > Disable Cache). However, that is not optimal, as every page resource gets downloaded every time you refresh the page.

So I did some reading up, and, just as I suspected, there is a Firefox configuration setting that forces a 304 request each time you refresh the page. This solution is superior to disabling the cache, as only resources that have changed are downloaded again.

If you have caching problems (or you're concerned that you might), you'll need to change a value in Firefox's "power-user" configuration settings. Here's how:
  1. Open a new tab, type about:config into your address bar, and promise Firefox you'll be careful. ;)
  2. Enter browser.cache.check_doc_frequency into the filter field.
  3. Change the value from 3 to 1, which tells Firefox to check the server for updated resources on every refresh.
Further Information: For the full reference see here, and for a list of the seven hidden pages in Firefox, see The Seven "Hidden" Pages.

Thursday, July 01, 2010

Complex CSS3 Expressions in MooTools: Reference Guide


In addition to standard CSS expressions . . . Selectors.js also allows you to select on element properties such as name, value, or href using standard CSS3 expressions (see http://www.w3.org/TR/css3-selectors/#attribute-representation).

Example:

// All the mail-to links on the page:
$$('a[href^=mailto:]');
The following expressions are supported:

= The property is equal to the value.
^= The property begins with the value.
$= The property ends with the value.
!= The property is not equal to the value.
*= The property contains the value.
~= The property is found when the value is split on spaces (so ~=foo matches “blah foo bar” but not “blahfoobar”).
|= The property is found when the value is split on dashes (so |=foo matches “blah-foo-bar” but not “blahfoobar”).

Here are a few more examples:
// All the inputs where name equals 'email'
$$('input[name=email]')
// All the images with urls that end in .gif
$$('img[src$=gif]')
// All the links without target="_blank"
$$('[target!=_blank]')
Note that these expressions can take double or single quotes when you want to search for something that has a space or other character:
$$('input[name!="user[username]"]')
$$('img[src$=".gif"]')

Source: Newton, Aaron. MooTools Essentials. Apress, 2008. 106–107.

Saturday, June 19, 2010

Slick Ajax Site for Creating Throbbers / Spinners

Ajax throbbers (or spinners as they are often informally called) are a hallmark of Web 2.0 applications, invaluable for showing the user that something is happening, at least when an accurate and detailed progress indicator is not required. Now, thanks to Yannick Croissant, creating custom variations is easy (and free).

You can set the foreground and background colors, or set to transparent (though depending on the colors you choose, it may cause the image to look a little ragged around the edges). What is more, this free service could not be easier to use.

Just go to ajaxload.info to get started. :)

A few examples that took less than a minute to create playing off the colors of this blog:

Wednesday, January 27, 2010

Cross-Browser: Getting/Setting the Class Attribute of an Element Using JavaScript

It is common knowledge that getting and setting the class of an element in Internet Explorer requires a bit of a work-around from the standard JavaScript solution that works with all other browsers.

The Usual Way

For example, using setAttribute to set the class requires two different values depending on whether the browser is Internet Explorer (className) or all others (class):
// a conditional statement specifies class or className
var klass = (isIE) ? 'className' : 'class';
element.setAttribute(klass, 'new_class');
The same is also true when attempting to get the class value with getAttribute:
element.getAttribute('className'); // for IE
element.getAttribute('class'); // everybody else
But what if you could both get and set the values on all browsers without any conditional logic? Apparently, you can.

A Better Way

The following solution works in all versions of IE from at least 6 onward, Firefox 3, Chrome, Safari, and Opera 10. I haven't tried it on Konqueror or any of the Linux browsers yet, but I would be surprised if it did not work there equally well.
element.className; // get class(es)
element.className = 'new_class'; // (re)set to single class

// or, if you don't want to tamper with any existing
// classes: note the space before the new class name
element.className = element.className + ' new_class';

// examples
if (/new_class/.test(element.className) { // test
    element.style.border = 'solid 1px #555';
}

if (someVariable == someValue) { // add "new_class" class
    element.className = element.className + ' new_class';
}
I have created a simple example page. In this case, the script toggles a show and hide class on table rows. Just download a copy from Google Docs, open in your litany of browsers, and enjoy. :)