3

If I run something like so:

var div1 = document.getElementById('div1'),
    div2 = document.getElementById('div2');

function setAnimation() {
    div1.style.webkitTransform = 'matrix(1,0,0,1,1200,0)';
    div2.style.webkitTransform = 'matrix(1,0,0,1,0,0)';
    div1.style.webkitTransition = div2.style.webkitTransition = '-webkit-transform 2s ease';
}

function startAnimation() {
    div1.style.webkitTransform = 'matrix(1,0,0,1,0,0)';
    div2.style.webkitTransform = 'matrix(1,0,0,1,-1200,0)';
}

 setAnimation();
startAnimation();​

div2 animates offscreen just fine, yet div1 remains in it's place at 0,0 and a change is never seen.

If I remove startAnimation altogether and change setAnimation to:

function setAnimation() {
    div1.style.webkitTransform = 'matrix(1,0,0,1,500,0)';
    div2.style.webkitTransform = 'matrix(1,0,0,1,-500,0)';
    div1.style.webkitTransition = div2.style.webkitTransition = '-webkit-transform 2s ease';
}

I would see both elements animate to those positions, both starting from 0,0.

It looks like an initial translation cannot be set dynamically within the same call stack as the setting of the transition? Or, more clearly, if a transition and transform are both set within the same call stack, transition will always take precedence.

Why is this?

  • I suppose one solution would be to call the second function inside a very short setTimeout, although perhaps your question was more _why_ this happens, not how to solve it. I'd be interested to know the best practice solution to this problem as well. – powerbuoy Feb 15 '12 at 23:50
  • Yeah, that's how I've been getting around it currently. It just feels dirty. – Mitch Anderson Feb 16 '12 at 16:17

1 Answers1

2

Because of the computation costs associated with each reflow, most browsers optimize the reflow process by queuing changes and performing them in batches. In this case, you are overwriting the inline style information of the elements, which the browser recognizes. The browser queues the first change and then the second before finally deciding that a reflow should occur, which it does all-at-once. (It doesn't matter that you have separated the changes into two function calls.) This would similarly happen when trying to update any other style property.

You can always force the browser to reflow by using any of the following:

  • offsetTop, offsetLeft, offsetWidth, offsetHeight
  • scrollTop, scrollLeft, scrollWidth, scrollHeight
  • clientTop, clientLeft, clientWidth, clientHeight
  • getComputedStyle() (currentStyle in IE)

So just change your first function to this:

var computedStyles = [];

function setAnimation() {
    div1.style.webkitTransform = 'matrix(1,0,0,1,1200,0)';
    div2.style.webkitTransform = 'matrix(1,0,0,1,0,0)';

    // force div's 1 and 2 to reflow
    computedStyles[div1] = div1.clientHeight;
    computedStyles[div2] = div2.clientHeight;
}

And now the browser will perform the initial transformations. You don't need two functions to accomplish this, just force the reflow in-between.

Due to some headaches that can occur when trying to solve reflow/repaint issues like this, I always suggest using CSS animations, even if you have to dynamically create and remove style rules from a stylesheet using the CSSOM. Read more here: programmatically changing webkit-transformation values in animation rules

Community
  • 1
  • 1
skyline3000
  • 7,639
  • 2
  • 24
  • 33
  • Wow, thanks a lot. That's one of those things that's been bugging me forever. I still have one more question though: Why does putting your transition in a setTimout (even with minimum delay) force the browser to reflow? – Mitch Anderson Mar 05 '12 at 23:21
  • Well I don't know for sure since I'm not a browser dev, but my best guess would be that it's the browser trying to be smart about when to reflow. If it has reflow changes queued and then a timout is about to come up, it takes that time to reflow the page (since it will be waiting anyway). I think the timeout itself is the trigger and that's why it doesn't matter that it could be for a 1ms or even 0ms delay. There has been a lot of success with setTimeout for this purpose I'm just not sure if it's as universally cross-browser as the other methods I've listed. – skyline3000 Mar 07 '12 at 17:01