setTimeout fix for -webkit-transition

Here's a simple animation which utilizes webkitTransition:

Click to animate

The code behind this example is not complicated:

element.style.webkitTransitionProperty = 'left';
element.style.webkitTransitionDuration = '2s';
element.addEventListener('click', function () {
    this.style.left = '100px';
    this.addEventListener('webkitTransitionEnd', function () {
        this.style.left = 0;
    });
});

The following example, though, does not act as one might expect!

Click to reposition

The code:

element.style.left = '100px';
element.style.webkitTransitionProperty = 'left';
element.style.webkitTransitionDuration = '2s';

Here are the instructions this code attempts to provide:

  1. Set the element's left value to '100px' (the page should immediately be redrawn).
  2. Set webkitTransitionProperty and webkitTransitionDuration, to apply a transition to future changes in the value of left.

What actually happens — as you'll have seen if you're viewing this page in a recent version of Safari or Chrome — is that the transition is applied to the preceding update. This behaviour strikes me as strange, but I have very little understanding of how these transitions are meant to be effected by the browser.

I did manage to get the element to behave as I had intended:

Click to reposition

The working code:

element.style.left = '100px';
setTimeout(function () {
    element.style.webkitTransitionProperty = 'left';
    element.style.webkitTransitionDuration = '2s';
}, 0);

For some reason wrapping the webkitTransition* declarations in an anonymous function passed to setTimeout with no delay prevents the transition from being applied retroactively. I wondered whether closure would be sufficient, but no, setTimeout seems to be the remedy for this "quirk".

I'd love to know whether the behaviour described here is correct behaviour. If I manage to find the answer to this I'll post an update. If you are able to enlighten me, please do so by leaving a comment!

Update —

I've just been watching one of the WWDC 2010 session videos, and it turns out the the fix I stumbled upon is actually the "correct" solution.

From Session 504 – CSS Effects, Part 2: Galleries and 3D Effects:

Aside: How Browsers Apply CSS Styles

  • Browsers optimize away redundant style changes
  • This matters with transitions, because they are temporal
var box = document.getElementById('box');
box.style.backgroundColor = 'red';
box.style.webkitTransition = 'background-color 2s';
window.setTimeout(function() {
  box.style.backgroundColor = 'blue';
}, 0);

Respond