Quantcast
Viewing latest article 14
Browse Latest Browse All 101

Using CSS3 animation with the JavaScript CSSOM API

One day IE7 and IE8 will die out and be a distant memory just like Netscape 4. JavaScript libraries like jQuery won't disappear but they'll be a lot less essential when all browsers conform to W3C standards.

However, what won't die out is the need for a JavaScript animation library. This requires mathematical knowledge that the everyday JS coder doesn't have.

CSS3 animation, although a welcome addition, has its limitations; although I'm sure that the spec will be built on and improved in the coming years.

It certainly does have its uses on the average web-build and it's something that I regularly use.

On my personal portfolio I have some CSS3 animation that runs in the left hand corner. I should state that creating animation that automatically runs on page load is a bad design decision because it distracts from the text, but it's my own personal site so I can experiment with it.

The code for this animation is particularly verbose. Here it is... gulp:

@keyframes dazzle {
    0% {
        box-shadow: -50px 40px 2px -13px rgba(255, 255, 255, 0), -60px 60px 2px -13px rgba(255, 255, 255, 1), -35px -20px 2px -12px rgba(255, 255, 255, 0), 40px 60px 3px -13px rgba(235, 255, 255, 1), 30px -50px 2px -13px rgba(255, 255, 255, 0), -50px 35px 3px -11px rgba(255, 255, 235, 1), -25px -20px 3px -13px rgba(235, 255, 235, 0), -40px 10px 4px -11px rgba(255, 255, 255, 1), -40px -65px 1px -13px rgba(255, 245, 255, 0), -10px 50px 3px -12px rgba(255, 255, 255, 1), 10px -30px 3px -12px rgba(235, 255, 255, 0), -30px -20px 2px -12px rgba(255, 245, 255, 1), 70px 40px 3px -13px rgba(255, 255, 255, 0), 20px 50px 2px -13px rgba(205, 255, 255, 1), -40px 45px 6px -11px rgba(255, 255, 255, 0), -35px -60px 3px -13px rgba(255, 255, 225, 1), -30px 40px 4px -11px rgba(255, 255, 255, 0), -70px -55px 1px -13px rgba(255, 255, 255, 1), -10px 50px 3px -12px rgba(235, 255, 255, 0), 20px -40px 3px -12px rgba(225, 255, 255, 1), -20px -10px 2px -13px rgba(255, 255, 255, 0), -50px -20px 5px -12px rgba(245, 255, 255, 1), 70px 30px 3px -13px rgba(215, 255, 245, 0), 40px 90px 2px -13px rgba(255, 255, 255, 1), -40px 35px 3px -11px rgba(165, 235, 215, 0), 35px -40px 3px -13px rgba(245, 255, 255, 1), -70px 20px 4px -11px rgba(215, 215, 235, 0), -40px -25px 1px -13px rgba(255, 245, 255, 1), 10px -60px 3px -12px rgba(255, 225, 245, 0), -20px 70px 3px -12px rgba(255, 255, 255, 1), -25px 80px 2px -13px rgba(245, 255, 255, 0), -40px 90px 2px -12px rgba(170, 235, 255, 1), -35px 20px 3px -13px rgba(225, 215, 255, 0), 60px -20px 3px -12px rgba(255, 255, 255, 1), 20px -80px 2px -13px rgba(255, 255, 255, 0), 10px 40px 3px -13px rgba(225, 255, 255, 1), -45px -40px 2px -13px rgba(255, 245, 255, 0), 40px -10px 3px -12px rgba(255, 255, 255, 1), 15px 30px 2px -13px rgba(225, 235, 255, 0), 10px -30px 2px -12px rgba(255, 245, 245, 1), 40px 50px 2px -13px rgba(255, 255, 255, 0), -55px 40px 4px -13px rgba(225, 245, 255, 1), -40px -10px 2px -12px rgba(195, 255, 175, 0), 30px -20px 2px -13px rgba(255, 255, 255, 1), 50px 20px 3px -13px rgba(255, 255, 255, 0), -55px -10px 2px -13px rgba(255, 205, 255, 1), 10px -30px 7px -12px rgba(255, 255, 255, 0), 10px 60px 2px -13px rgba(255, 255, 255, 1), 20px -30px 2px -12px rgba(225, 225, 225, 0), 40px 50px 2px -13px rgba(195, 255, 255, 1), 30px 70px 3px -13px rgba(235, 255, 235, 0), -60px 60px 2px -13px rgba(255, 255, 255, 1), 10px 70px 3px -12px rgba(225, 205, 255, 0), 20px 90px 2px -13px rgba(235, 255, 235, 1), 30px 70px 5px -12px rgba(225, 255, 225, 0), 10px -40px 2px -12px rgba(195, 255, 175, 1), 20px -60px 2px -13px rgba(255, 255, 255, 0), 30px 60px 3px -13px rgba(255, 255, 255, 1), -75px -30px 2px -13px rgba(255, 205, 255, 0), 30px -30px 7px -12px rgba(255, 255, 255, 1), 40px 60px 2px -13px rgba(255, 255, 255, 0), 20px -30px 2px -12px rgba(225, 225, 225, 1), 20px 50px 2px -13px rgba(195, 255, 255, 0), 40px 20px 3px -13px rgba(235, 255, 235, 1), -40px 30px 2px -13px rgba(255, 255, 255, 0);
    }

    100% {
        box-shadow: -50px 40px 2px -13px rgba(255, 255, 255, 1), -60px 60px 2px -13px rgba(255, 255, 255, 0), -35px -20px 2px -12px rgba(255, 255, 255, 1), 40px 60px 3px -13px rgba(235, 255, 255, 0), 30px -50px 2px -13px rgba(255, 255, 255, 1), -50px 35px 3px -11px rgba(255, 255, 235, 0), -25px -20px 3px -13px rgba(235, 255, 235, 1), -40px 10px 4px -11px rgba(255, 255, 255, 0), -40px -65px 1px -13px rgba(255, 245, 255, 1), -10px 50px 3px -12px rgba(255, 255, 255, 0), 10px -30px 3px -12px rgba(235, 255, 255, 1), -30px -20px 2px -12px rgba(255, 245, 255, 0), 70px 40px 3px -13px rgba(255, 255, 255, 1), 20px 50px 2px -13px rgba(205, 255, 255, 0), -40px 45px 6px -11px rgba(255, 255, 255, 1), -35px -60px 3px -13px rgba(255, 255, 225, 0), -30px 40px 4px -11px rgba(255, 255, 255, 1), -70px -55px 1px -13px rgba(255, 255, 255, 0), -10px 50px 3px -12px rgba(235, 255, 255, 1), 20px -40px 3px -12px rgba(225, 255, 255, 0), -20px -10px 2px -13px rgba(255, 255, 255, 1), -50px -20px 5px -12px rgba(245, 255, 255, 0), 70px 30px 3px -13px rgba(215, 255, 245, 1), 40px 90px 2px -13px rgba(255, 255, 255, 0), -40px 35px 3px -11px rgba(165, 235, 215, 1), 35px -40px 3px -13px rgba(245, 255, 255, 0), -70px 20px 4px -11px rgba(215, 215, 235, 1), -40px -25px 1px -13px rgba(255, 245, 255, 0), 10px -60px 3px -12px rgba(255, 225, 245, 1), -20px 70px 3px -12px rgba(255, 255, 255, 0), -25px 80px 2px -13px rgba(245, 255, 255, 1), -40px 90px 2px -12px rgba(170, 235, 255, 0), -35px 20px 3px -13px rgba(225, 215, 255, 1), 60px -20px 3px -12px rgba(255, 255, 255, 0), 20px -80px 2px -13px rgba(255, 255, 255, 1), 10px 40px 3px -13px rgba(225, 255, 255, 0), -45px -40px 2px -13px rgba(255, 245, 255, 1), 40px -10px 3px -12px rgba(255, 255, 255, 0), 15px 30px 2px -13px rgba(225, 235, 255, 1), 10px -30px 2px -12px rgba(255, 245, 245, 0), 40px 50px 2px -13px rgba(255, 255, 255, 1), -55px 40px 4px -13px rgba(225, 245, 255, 0), -40px -10px 2px -12px rgba(195, 255, 175, 1), 30px -20px 2px -13px rgba(255, 255, 255, 0), 50px 20px 3px -13px rgba(255, 255, 255, 1), -55px -10px 2px -13px rgba(255, 205, 255, 0), 10px -30px 7px -12px rgba(255, 255, 255, 1), 10px 60px 2px -13px rgba(255, 255, 255, 0), 20px -30px 2px -12px rgba(225, 225, 225, 1), 40px 50px 2px -13px rgba(195, 255, 255, 0), 30px 70px 3px -13px rgba(235, 255, 235, 1), -60px 60px 2px -13px rgba(255, 255, 255, 0), 10px 70px 3px -12px rgba(225, 205, 255, 1), 20px 90px 2px -13px rgba(235, 255, 235, 0), 30px 70px 5px -12px rgba(225, 255, 225, 1), 10px -40px 2px -12px rgba(195, 255, 175, 0), 20px -60px 2px -13px rgba(255, 255, 255, 1), 30px 60px 3px -13px rgba(255, 255, 255, 0), -75px -30px 2px -13px rgba(255, 205, 255, 1), 30px -30px 7px -12px rgba(255, 255, 255, 0), 40px 60px 2px -13px rgba(255, 255, 255, 1), 20px -30px 2px -12px rgba(225, 225, 225, 0), 20px 50px 2px -13px rgba(195, 255, 255, 1), 40px 20px 3px -13px rgba(235, 255, 235, 0), -40px 30px 2px -13px rgba(255, 255, 255, 1);
    }
}

I was wondering if I could change the rgba values whenever a visitor hovers their mouse over one of the nine main code blocks. Changing animation on mouseover other than hovering over a link is a bad usability decision, but again, as it's my personal portfolio I have room to experiment.

One option is to create different animation blocks in the CSS file and use JavaScript to change the class. Theoretically this is the correct approach because it keeps the CSS and JS separate. However, I really didn't fancy creating multitudes of complex CSS code. (Another possible option is to use SASS to auto-generate the code, but I'll leave that for another time).

So I set about creating a JavaScript module that can create random rgba values and then change the animation blocks on an event change.

If you want to create random digits in JavaScript then the Math object is your friend. This in-built JS object is very inexpensive to use.

So using Math I sketched out the basis for a JS module to create random rgba values (this code was changed in the final version):

ANDY.ANIMATION = (function () {
    'use strict';
    var _private = {
        headers: null,
        randomOne: function () { /* 70 to -70 */

            return Math.floor(Math.random() * 141) - 70;

        },
        randomTwo: function () { /* 2 to 6 */

            /* recursive function. Only return numbers from 2 to 6 */

            var random = Math.floor(Math.random() * 7);

            if (random < 2) {

                return arguments.callee(true);

            } else {

                return random;

            }

        },
        randomThree: function () { /* -11 to -13 */

            var numbers = [-11, -12, -13];

            return numbers[Math.floor(Math.random() * numbers.length)];

        },
        randomFour: function () { /* 0 to 255 */

            return Math.floor(Math.random() * 256);

        },
        randomFive: function () { /* 0 or 1 */

            return Math.floor(Math.random() * 2);


        },
        set: function (val) {
            this.headers = val;
            _private.run();
        },
        setNumbers: function () {



        }
    };
    return {
        init: function () {

            console.log(_private.randomTwo());

        }
    };
}());

Using this as a starting point I created a method to generate the CSS block. The do-while loop builds up the array of rgba values which is finally returned as a string.

 setNumbers: function () {

     /* create 66 different values for the style 
          sheet like so: -50px 40px 2px -13px rgba(255, 255, 255, 0) */
     var x, l, finalArray = [];

     x = 0;
     l = 66;

     do {

         finalArray.push(this.randomOne() + 'px ' + this.randomOne() + 'px ' + this.randomTwo() + 'px ' + this.randomThree() + 'px ' + 'rgba(' + this.randomFour() + ',' + this.randomFour() + ',' + this.randomFour() + ',' + this.randomFive() + ')');

         x += 1;

     } while (x < l);

     return finalArray.toString();

 },

My method next looped through my CSS files to find the file (768.css) that contained the animation and I saved it to the memory of the object (the this keyword refers to the parent object):

 findStyleSheet: function () {

     /* find the 768.css stylesheet */

     var x, l;

     for (x = 0, l = this.styleSheets.length; x < l; x += 1) {

         if (this.styleSheets[x].href) {

             if (this.styleSheets[x].href.indexOf('768') !== -1) {

                 this.styleSheetExtra = this.styleSheets[x];
                 break;

             }

         } // end if

     } // end for loop

 },

Then I used CSSOM to find the keyframe rule:

 findKeyframesRule: function (rule) {

     /* in the 768.css stylesheet find the animation*/

     var ruleList = this.styleSheetExtra.cssRules,
         i, key, l;

     // loop through the cssRules object 

     for (i = 0, l = ruleList.length; i < l; i += 1) {

         for (key in ruleList[i].cssRules) {

             // loop through ruleList object literals
             if (ruleList[i].cssRules.hasOwnProperty(key)) {

                 // find the keyframe animation
                 if (ruleList[i].cssRules[key].type === window.CSSRule.WEBKIT_KEYFRAMES_RULE && ruleList[i].cssRules[key].name === rule) {

                     return ruleList[i].cssRules[key];

                 } // end if

             } // end if

         } // end for object

     } // end for ruleList 

     return null;
 },

This was difficult code to get exactly right but perseverance pays off. I tried window.CSSRule.WEBKIT_KEYFRAMES_RULE and window.CSSRule.MOZ_KEYFRAMES_RULE but window.CSSRule.KEYFRAMES_RULE returned undefined although both browsers are supposed to have implemented a non-prefixed version.

Here is the CSS3 animation change that happens on the choosen event, using the previously created setNumbers() method:

changeRule: function () {

    var keyframes = this.findKeyframesRule("dazzle"),
        cloneBlock, parentBlock, domNode;

    if (keyframes !== null) {

        // change the CSS3 animation here

        keyframes.deleteRule("0%");
        keyframes.deleteRule("100%");
        keyframes.insertRule("0% { box-shadow:" + this.setNumbers() + "; }");
        keyframes.insertRule("100% { box-shadow:" + this.setNumbers() + "; }");

        /* it is necessary to redraw the block for the new animation to work */

        domNode = doc.querySelector('div[role=banner] h1');

        cloneBlock = domNode.cloneNode(true);
        parentBlock = domNode.parentNode;
        parentBlock.removeChild(domNode);
        parentBlock.appendChild(cloneBlock);

    }

},

Here comes the event methods:

onHover: function () {

    var wrapper = doc.querySelector('div[role=main]');

    wrapper.addEventListener('mouseover', _private.onHoverEvent, false);

},

findUpId: function (elem) {

    while (elem.parentNode) {
        elem = elem.parentNode;
        // only use the right node type and that which has an id attribute
        if (elem.nodeType === 1 && elem.hasAttribute('id')) {
            // only if the id attribute value contains block
            if (elem.getAttribute('id').indexOf('block') !== -1) {
                return elem.getAttribute('id');
            }
        }
    }
    return null;

},

onHoverEvent: function (event) {

    // check again to make sure the correct id is choose
    var domResult, idArray = ['block-1', 'block-2', 'block-3', 'block-4', 'block-5', 'block-6', 'block-7', 'block-8', 'block-9'];

    domResult = _private.findUpId(event.target);

    // check again to make sure that the right id block is used 
    if (domResult !== null && idArray.indexOf(domResult) !== -1) {

        // place chosen id into object memory
        if (_private.dom === null || _private.dom !== domResult) {

            _private.changeRule();
            _private.dom = domResult;

        } // end if

    } // end if

}

The coding theory behind this is to set the event on the parent element – div[role=main]. On mouseover I wanted to use the parent id block of the item, ie block-1 or block-2, then change the animation. The choosen DOM block needed to be kept into the object memory on mouseover because I only want to fire the changeRule() method once for every div block.

The result? Not amazing. The problem is that the animation starts from the beginning rather than the colours changing mid-animation so it creates a stuttering affect. The needs to be a bit of morphing happening! To alleviate that I added an opacity and webkit filter blur rule to the animation.

The full code can be found on this GitGist.


Viewing latest article 14
Browse Latest Browse All 101

Trending Articles