Ensure triggering transitionend event in JavaScript

  Pi Ke        2015-05-09 08:56:51       8,806        5    

CSS3 Transition has been widely used in modern web app development to offer users animations. Traditionally animations of element in HTML are controlled by JavaScript. If fancy animation is desired, then third party plugins can be installed in browsers such as Flash, Silverlight, Java Applet etc. With CSS3, animations can be easily achieved like a charm.

Transition is one of the many features provided by CSS3. It can be used to transit one element from one state to another state smoothly within a specified time. Usually we are not satisfied with just seeing the animation, we frequently want to perform some actions when the transition completes. Fortunately, there is a event in JavaScript named transitionend which can achieve just this.

To register an transitionend event, an element has to be bound to it by specifying:

element.addEventListener("transitionend", callback, false);

It's a normal event listener registration process. However, this event is not currently supported by all browsers yet. Many browsers provide their own event name to represent the transitionend event. To be cross browser compatible, the proper way of registering transitionend event would be:

function registerTransitionEndEvent(obj, callback){
	var transitions = {
        'WebkitTransition' : 'webkitTransitionEnd',
        'MozTransition'    : 'transitionend',
        'MSTransition'     : 'msTransitionEnd',
        'OTransition'      : 'oTransitionEnd',
        'transition'       : 'transitionend'
    };
    for(var t in transitions){
        if(obj.style[t] !== undefined){
            obj.addEventListener(transitions[t], function(event){
            	callback();
			}, false);
			break;
        }
    }
}

After registering this event, the only thing needs to be done to trigger the transitionend event is to change the property where transition is watching for. For example:

div {
    width: 100px;
    height: 100px;
    background: red;
    -webkit-transition: width 2s; /* Safari */
    transition: width 2s;
}

The above CSS defines a transition property which has value of width 2s which means if the width of the div is changed, the change will complete in 2 seconds starting from the initial width to the final width. If you changed the width of the div somewhere and after that transition completes, the transitionend event will be triggered.

Below is a real example showing how this works.

Demo transitionend event<span id="demo-pane"> </span>
<script type="text/javascript">// <![CDATA[
function registerTransitionEndEvent(obj, callback){
    var transitions = {
        'WebkitTransition' : 'webkitTransitionEnd',
        'MozTransition'    : 'transitionend',
        'MSTransition'     : 'msTransitionEnd',
        'OTransition'      : 'oTransitionEnd',
        'transition'       : 'transitionend'
    };
    for(var t in transitions){
        if(obj.style[t] !== undefined){
            obj.addEventListener(transitions[t], function(event){
                callback();
            }, false);
            break;
        }
    }
}
window.onload = function(){
    var span = document.getElementById("demo-pane");
    registerTransitionEndEvent(span, function(event){
        console.log("Transition end event captured");
    });
 
    span.style.top = "100px";
    span.style.transform = "translateY(-100px)";
    setTimeout(function(){retransition();}, 1000);
};
function retransition(){
    var span = document.getElementById("demo-pane");
    var rand = 1.0 + (Math.random()/10000);
    span.style.transform = "translateY(0px)";
    setTimeout(function(){retransition();}, 1000);
}
// ]]></script>

Above HTML page will create a span with red background and the JavaScript registers a transitionend event on this span and then when the page is loaded, the transition will begin with the line 

span.style.transform = "translateY(-100px)";

This line will transform the span from its current position to a new position which is 100px above it. This is a transition, it will be completed in 0.5s. After this transition completes, callback will be called and line "Transition end event captured" will be printed.

Then after 1 second, retransition() will be called and the transform is changed again, so the transitionend will be triggered again. And line "Transition end event captured" be printed again.

Here comes the tricky part, do you know how many times the line "Transition end event captured" will be printed if the page continues to run? The answer is 2. How? The reason is being that transitionend will be triggered only if the transition happens. In the above case, when the retransition() is called recursively, the transform is actually not changed anymore after the first change. Hence no transition happens anymore.

In some cases, you want to ensure that the transitionend event is always triggered when one function is called, you need to introduce some dummy transition so that the browser can detect there is transition and the transitionend event will be triggered.

Below is a modified version of the retransition() function which can ensure transitionend event is triggered 

function retransition(){
	var rand = 1.0 + (Math.random()/10000);
	var span = document.getElementById("demo-pane");
	span.style.transform = "translateY(0px) scaleX("+rand+")";
	setTimeout(function(){retransition();}, 1000);
}

Transition is a very fancy feature in CSS3. If used properly,it can bring great user experience. But if used improperly, it will turn to you and bite you. Do more research before applying transition in your apps and do more testing in different browsers.

CSS3 TRANSITION  TRANSITIONEND  FORCE FIRE TRANSITIONEND 

       

  RELATED


  5 COMMENTS


Anonymous [Reply]@ 2017-08-25 15:55:26

The two retransition() functions are identical; the only thing different about the second one is whitespace.

Ke Pi [Reply]@ 2017-08-26 02:29:08

Thank you pointing this out. Have updated the first one.

Anonymous [Reply]@ 2017-08-28 13:56:33

Thank you for updating your code, two other notes, however:

  1. In your first declaration of retransition() you have the line "var rand = 1.0 + (Math.random()/10000);" but it's never used, this adds some confusion.
  2. In your use of setTimeout(), you're wrapping the function retransition() in another function; this does not need to be done. Since all you're doing is wanting to call retransition(), just pass that function as the first parameter to setTimeout, like this: "setTimeout(retransition, 1000)".
Anonymous [Reply]@ 2017-11-17 02:38:48

though its possible to use a random number to force the transition, i think we still cant use `scale(1.00000001,1)`, because it has changed the scale even if its not that obivous

Ke Pi [Reply]@ 2017-11-17 06:42:49

You are correct. The key point here is that this change is usually unnoticeable by human beings but it's critical for the browser to trigger repaint because it has to know something has changed. 



  RANDOM FUN

Falling into a trap