Properly degrading JavaScript Effects with Script.aculo.us

I recently decided to use Script.aculo.us in a project that I’m working on. I was actually pretty impressed with it’s cross-browser compatibility and ease of use. I didn’t even think that the 200K size (including the Prototype Framework) was that bad. However, I did run into some issues while trying to make my site function properly for users that have JavaScript disabled, while still allowing the effects for those that do.

In my particular situation, I wanted to make some objects appear with an effect. You will notice that if you apply an Appear effect to an element, it will display, then disappear, then appear with the effect. The solution is to set an inline style with display:none, except that this will cause the element to be missing from users without JavaScript. The solution? Add the display:none style with Javascript.

First, I created a CSS class called effect_appear, with no styles.

.effect_appear {
}

Then I use Javascript to modify that class, and set the display to none.

function getStyleClass (className) {
    if (document.styleSheets.length < 1) {
        return null;
    }
    if (document.styleSheets[0].cssRules) {
        var cssRules = 'cssRules';
    } else {
        var cssRules = 'rules';
    }
    for (var s = 0; s < document.styleSheets.length; s++) {
        for (var r = 0; r < document.styleSheets[s][cssRules].length; r++) {
            if (document.styleSheets[s][cssRules][r].selectorText == '.' + className) {
                return document.styleSheets[s][cssRules][r];
            }
        }
    }
    return null;
}
getStyleClass('effect_appear').style.display = 'none';

Now, any element with that class will display normal to a user without JavaScript support, but will be missing for users with JavaScript. Next I added a ‘load’ event handler to the window element, which will make the elements appear with an effect.

Event.observe(window, 'load', effects);
function effects() {
    document.getElementsByClassName('effect_appear').each(
        function (el) {
            el.visualEffect('Appear');
        }
    );
}

However, there is still one problem. Because the style we created is not inline, it seems to override any effect that we add. The solution is to first remove the classname, and hide() the element (this will remove the class, but keep the element hidden, without the user ever seeing the element). Then we apply the visual effect (in this case, Appear).

Event.observe(window, 'load', effects);
function effects() {
    document.getElementsByClassName('effect_appear').each(
        function (el) {
            el.removeClassName('effect_appear').hide().visualEffect('Appear');
        }
    );
}

Here is the whole thing. Remember, the class “effect_appear” MUST exist in one of your stylesheets, or in a style tag ABOVE this code.

function getStyleClass (className) {
    if (document.styleSheets.length < 1) {
        return null;
    }
    if (document.styleSheets[0].cssRules) {
        var cssRules = 'cssRules';
    } else {
        var cssRules = 'rules';
    }
    for (var s = 0; s < document.styleSheets.length; s++) {
        for (var r = 0; r < document.styleSheets[s][cssRules].length; r++) {
            if (document.styleSheets[s][cssRules][r].selectorText == '.' + className) {
                return document.styleSheets[s][cssRules][r];
            }
        }
    }
    return null;
}
getStyleClass('effect_appear').style.display = 'none';
Event.observe(window, 'load', effects);
function effects() {
    document.getElementsByClassName('effect_appear').each(
        function (el) {
            el.removeClassName('effect_appear').hide().visualEffect('Appear');
        }
    );
}

17 thoughts on “Properly degrading JavaScript Effects with Script.aculo.us

  1. This is certainly the simplest way of achieving degradable Script.aculo.us effects that require the style display:none; I’ve come accross so far! Excellent snippet of code, and thanks for sharing 🙂

  2. Hi, thanks so much! This is fantastic.

    I just changed your script a little little bit by deleting the last line of script “.visualEffect(‘Appear’) so that I could toggle effects (blind down/blind up) after the page has loaded. The script below works fine in IE7 and Firefox.

    However in Safari for some reason the script works the first time, then on reload it doesn’t hide the divs. Is there something wrong with my edited script? Or is this just a Safari problem?

    function getStyleClass (className) {
    if (document.styleSheets.length

  3. hmm. your textbox is too small… ok so here’s the script once again, just where I changed it…

    Event.observe(window, ‘load’, effects);
    function effects() {
    document.getElementsByClassName(‘effect_appear’).each(
    function (el) {
    el.removeClassName(‘effect_appear’).hide();
    }
    );
    }

    cheers

  4. I’m currently using scriptaculous with fastinit on my splashpage and am getting a delay before this script kicks in (there’s a form that appears and then disappears). Oddly enough, it seems to only effect one div, and the others which have the effect_appear class work perfectly. Any thoughts?

  5. Just to follow up, I use Mint and had Mint’s script linked just before the closing body tag (at one point it was conflicting with an older script and it helped to move it there). I moved it back to just before the closing head tag and the delay seems to have dissappeared. Thanks for the nice work!

  6. Great piece of code. I can sure use this. However, I have a slightly related scriptaculous effect that I’m trying to implement, but it just won’t work. I’m hoping xavisys or any js guru reading this can help.
    Here’s what I’m trying to do:
    (This does not work)

    function scroll() {
    //Scope: books
    new Effect.Fade(‘six’, {queue: {duration: 3.0, scope: ‘books’} });
    new Effect.SlideDown(‘seven’, {queue: {position:‘end’, scope: ‘books’} });
    new Effect.Fade(‘seven’, {queue: {position: ‘end’, scope: ‘books’} });
    new Effect.BlindDown(‘six’, {queue: {position: ‘end’, scope: ‘books’} });
    //Scope: center
    new Effect.Fade(‘nine’, {queue: {duration: 3.0, scope: ‘center’} });
    new Effect.Appear(‘ten’, {queue: {position:‘end’, scope: ‘center’} });
    new Effect.Fade(‘ten’, {queue: {position: ‘end’, scope: ‘center’} });
    new Effect.Appear(‘eleven’, {queue: {position:‘end’, scope: ‘center’} });
    new Effect.Fade(‘eleven’, {queue: {position: ‘end’, scope: ‘center’} });
    new Effect.Appear(‘nine’, {queue: {position: ‘end’, scope: ‘center’} });
    new PeriodicalExecuter(scroll, 30);

    Here’s a simpler version that works:

    function scroll() {
    new Effect.Fade(‘six’, { duration: 3.0 });
    new Effect.SlideDown(‘seven’, { queue: ‘end’, duration: 6.0 });
    new Effect.Fade(‘seven’, { queue: ‘end’, duration: 6.0 });
    new Effect.BlindDown(‘six’, { queue: ‘end’, duration: 6.0 });
    }
    new PeriodicalExecuter(scroll, 30);

    I have zero js coding skill. Will appreciate any help fine tuning the first one.

    TIA

    1. It would help for you to describe what you WANT to happen, and what DOES happen. However, in the simple example, you are passing options like this:
      { queue: “end”, duration: 6.0 }
      Which adds it to the end of the effects queue, and then makes the effect take 6 seconds to complete. In your top example, you pass options like this:
      {queue: {position:”end”, scope: “books”} }
      Which is invalid. The queue parameter needs to be numerical, and you are passing it a hash (contained in {}) Try something like this:
      {queue: ‘end’, scope: ‘books’}

      1. thanks for your response. Here’s what I’m trying to do:
        I have 2 groups of divs, each group in a separate box. The first div in each group is what is visible when the page loads, and the others have ‘display:none’ set. What I want is for the first div to fade out , say, in 6 or 3 seconds, for the next one to appear and then fade out just like the first one, and then the next and so on. Obviously, I’m going to have 2 scopes.
        What’s eating me is the syntax of having queue, scope, and duration in one effect.

        Hope my explanation makes sense.

        TIA

        1. I see what you want to do now. However, I still think it’s a syntax problem. Try this:
          {queue:{QUEUE OPTIONS ONLY}, other options}
          So:
          {queue: {duration: 3.0, scope: “books”} }
          becomes:
          {queue: {scope: ‘books’}, duration: 3.0}

  7. I’m using getStyleClass to hide elements on my page but when I print my page, all the elements are displayed. Is their a way around that?

  8. dude , today everyone has to have js enabled, and people who know nothing about web wont know and never wonder about to disable this thingy called js

  9. Anonymous: Statistically speaking, there are plenty of people without JS. Some are on mobile browsers that don;t support it, some have it turned off, etc.

    terry: Please keep the language clean. It looks like somewhere during a WordPress upgrade, some characters got replaced by their entities. I fixed it. Thank you for pointing it out.

Leave a Reply

Your email address will not be published. Required fields are marked *