Transition CSS

Petite expérimentation sur la gestion des transitions CSS d'un élément, avec identification de la fin de toutes les transitions.

Test de transition d'un élément (Firefox 12, Chromium 18, Opera 12 beta).
define(['rnb/core/env'], function(env) {
    /**
     *  Objet qui permet d'être à l'écoute de la fin de toutes les transitions
     *  appliquées à un élément HTML.
     *
     *  L'événement transitionend est émis à chaque fois qu'une propriété a terminé
     *  sa transition. Cet objet permet d'être avertit uniquement lorsque toutes
     *  les transitions  sont terminées.
     *
     *  @constructor
     *  @param {HTMLElement} el Elément sur lequel écouter les transition.
     *  @param {Function} listener Ecouteur appelé en fin de transition.
     */
    var Transition = function (el, listener, duration) {
        /**
         *  Elément sur lequel porte la transition
         *  @private
         *  @type HTMLElement
         */
        this.element = el;
        /**
         *  Ecouteur de transition
         *  @private
         * @type Function
         */
        this.listener = listener;
        /**
         *  @private
         *  @type int
         */
        this.duration = duration || 0;
        /**
         *  Binding de l'écouteur de fin de transition
         *  @private
         *  @type Function
         */
        this.endBinding = this.end.bind(this);
        
        this.init();
    };
    
    /**
     *  Nom de l'événement de fin de transition, variant selon les navigateurs
     *  @type String
     */
    Transition.EVENT = env.transitionEndEvent();
    
    /**
     *  Prototype
     */
    Transition.prototype = {
        // fix constructor
        constructor: Transition,
        /**
         *  Initialisation de lécoute des transition, appelée automatiquement lors
         *  de la création de l'objet.
         */
        init: function () 
        {
            this.findDuration();
        },
        
        /**
         *  Arrêt de l'écoute des transitions.
         */
        dispose: function () 
        {
            this.element = null;
            this.listener = null;
        },
        
        start: function()
        {
            if (this.duration > 0) {
                window.setTimeout(this.endBinding, this.duration);
            } else {
                this.end();
            }
        },
        
        end: function ()
        {
            this.listener();
        },
        
        findDuration: function()
        {
            if (!this.element.parentNode) {
                window.setTimeout(this.findDuration.bind(this), 50);
                return;
            }
            var styles = window.getComputedStyle(this.element, null),
                props = env.vendorRules('dom', 'transitionDuration'),
                rules = styles[props[1]] || styles[props[0]],
                duration = 0,
                cDuration = 0,
                times = null,
                i = 0,
                n = 0;
            if (rules) {
                times = rules.split(',');
                n = times.length;
                for (i = 0; i < n; i++) {
                    cDuration = parseFloat(times[i].trim());
                    if (cDuration > duration) {
                        duration = cDuration;
                    }
                }
            }
            this.duration = duration * 1000;
        },
        
        /**
         *  Trouver la liste des propriétés animées et en déduire leur nombre
         *  @private
         */
        findProperties: function() 
        {
            var styles = window.getComputedStyle(this.element, null),
                props = env.vendorRules('dom', 'TransitionProperty'),
                rules = styles[props[1]] || styles[props[0]];
            if (rules) {
                this.props = rules.split(',').length;
                this.element.addEventListener(Transition.EVENT, 
                        this.transitionListener, false);
            }
        },
        
        /**
         *  Ecouteur de transition
         *  @param {DOMEvent} e
         */
        onTransitionEnd: function (e) 
        {
            this.endEvents++;
            if (this.endEvents === this.props) {
                this.endEvents = 0;
                this.listener(e);
            }
        }
    };

    // exports
    return Transition;
});
Objet de gestion des transitions CSS d'un élément.
<div id="box-test" class="start"></div>
<p><button id="play">PLAY</button></p>
Code HTML du test.
#box-test { 
    height: 50px; 
    -webkit-transition-property: width, background-color;
       -moz-transition-property: width, background-color;
        -ms-transition-property: width, background-color;
         -o-transition-property: width, background-color;
            transition-property: width, background-color;
    -webkit-transition-duration: 1s;
       -moz-transition-duration: 1s;
        -ms-transition-duration: 1s;
         -o-transition-duration: 1s;
            transition-duration: 1s;
}
.start { 
    background-color: #f00; width: 50px; 
}
.transition {
    width: 400px;
    background-color: #00f;
}
Code CSS du test.
require(['rnb/ui/utils/Transition'], function(Transition) {
    var box = document.getElementById('box-test'),
        btn = document.getElementById('play'),
        trans = new Transition(box, function() {
            box.classList.remove('transition');
            btn.disabled = false;
        });
    btn.addEventListener('click', function (e) {
        btn.disabled = true;
        box.classList.add('transition');
    });
});
Code javascript du test.