Mike Chambers

code = joy

Timing issues when animating with CSS3 Transitions

with 8 comments

I have been playing around a lot lately with motion graphics created using HTML5 and / or CSS3. One of my favorite new features is CSS Transitions, which makes it super simple to animate element properties between two states.

However, I ran into a gotcha the other day, and wanted to make a quick blog post in case anyone else runs into it in the future. Basically, if you change a property that a CSS Transition is monitoring in the same script loop that you add the element to the DOM, the CSS Transition will not take affect. Instead, the element will be drawn with the new properties, and will not animate to those properties.

Here is an example that shows the issue, as well as how to fix it.

First the relevant styles for the CSS Transition:

.box
{
	/*...*/
	-webkit-transition: all 0.7s ease;
	-moz-transition: all 0.7s ease;
	-o-transition: all 0.7s ease;
	transition: all 0.7s ease;
}

 

Notice how when you click the example the cirlce just appears on the right, instead of appearing on the left, and then animating to the right as it should.

Here is the relevant code that creates the div and updates it position:

function onMouseClick()
{
	if(box)
	{
		//remove existing box from dom
		document.body.removeChild(box);
	}
 
	//create the div
	box = document.createElement("div");
	box.className = "box";
 
	//add to dom
	document.body.appendChild(box);
 
	//position in middle / left of screen
	box.style.left = "10px";
	box.style.top = (window.innerHeight / 2) + "px";
 
	//set position we want div to animate to (using the CSS Transition)
	//NOTE : This will not animate, but will instead just be moved / drawn
	//in the final position
	//need to delay this call by 20 ms
	box.style.left = (window.innerWidth - 100) + "px";
}

View / Download Code

The solution, is to delay changing the properties that you want to animate by a short time interval. I have found that for most browsers a delay of as little as one second is enough to allow the CSS transition to take effect. However, for Firefox, the first time an element is added, I had to add a delay of 20ms in order to ensure the element animated correctly. However, subsequent animations could use a 1 ms delay. I am not sure if the delay interval is browser specific, or is related to how fast the users CPU is.

Setting the delay can be done using setTimeout.

Here is the update example, and code:

 

function onMouseClick()
{
	if(box)
	{
		//remove existing div from dom
		document.body.removeChild(box);
	}
 
	//create the div
	box = document.createElement("div");
	box.className = "box";
 
	//add to dom
	document.body.appendChild(box);
 
	//position in middle / left of screen
	box.style.left = "10px";
	box.style.top = (window.innerHeight / 2) + "px";
 
	//delay updating position so it will animate correctly
	setTimeout(moveBox, 20, box);
}
 
function moveBox(box)
{
	box.style.left = (window.innerWidth - 100) + "px";
}

View / Download Code

The only change, is that we moved the code to update the divs position to its own function. This allows us to then use setTimeout to delay the code from being called for 1 ms. By doing this, the div will be added to the dom, and positioned in the correct initial position, and then animate to its new position.

Here is another example that uses setTimeout to animate newly created DIVs.

 

View / Download Code

Post in the comments if you have any questions or suggestions.

Written by mikechambers

July 20th, 2011 at 12:54 pm

Posted in General

Tagged with , , , ,

8 Responses to 'Timing issues when animating with CSS3 Transitions'

Subscribe to comments with RSS or TrackBack to 'Timing issues when animating with CSS3 Transitions'.

  1. Any chance you could hook into the DOMNodeInserted event instead of relying on a fixed time delay?

    Sho Kuwamoto

    20 Jul 11 at 1:10 pm

  2. Yeah. You should be able to do that (or listen for the next animation frame event). I did the delay because the code is a bit easier.

    Ill do a quick test though with the event and post the results.

    mikechambers

    20 Jul 11 at 1:29 pm

  3. ok. I put together a quick test with the DOMNodeInsertedIntoDocument event. The event is fired. However, the weird thing is that, the div’s position isnt updated at all:

    function onMouseClick()
    {
    	if(box)
    	{
    		//remove existing div from dom
    		document.body.removeChild(box);
    	}
    	
    	//create the div
    	box = document.createElement("div");
    	box.className = "box";
    
    	box.addEventListener("DOMNodeInsertedIntoDocument", function(e){moveBox(box); console.log(box)});
    	
    	//position in middle / left of screen
    	box.style.left = "10px";
    	box.style.top = (window.innerHeight / 2) + "px";
    }

    Basically, in this case, the DIV will stay in its original position, and not the position it should have animated to.

    I might be doing something wrong here, and am going to keep playing around with it.

    mikechambers

    20 Jul 11 at 1:45 pm

  4. a value of 0 in the setTimeout does not work ?

    I guess it’s a mater of reflow / repaint : what happens if instead of :

    
    box.style.left = "10px";
    box.style.left = (window.innerWidth - 100) + "px";
    

    You do something in the middle, like

    box.style.left = "10px";
    // try to reflow
    box.innerHTML = ' ';
    box.innerHTML = '';
    box.style.left = (window.innerWidth - 100) + "px";
    

    jpvincent

    21 Jul 11 at 3:11 am

  5. Try creating a style declaration in js, then creating the dom node to match your styling’s selector, then adding the animation class style. That should trigger the appropriate rendering steps without the need for setTIMEOUT.

    Using a set of three closures would work as well, one for setup, one for initial styling, and one for applying the animation. From your first, call the second with setTimeout (myInitialStyling, 0); And the same for the second to the third. This will set the functions to run the next time the VM is free, which should occur after the reflow triggered by node insertion, then by styling, etc.

    Jack Viers

    21 Jul 11 at 10:50 pm

  6. Once again, this is absolutely normal considering the reflow/repaint mechanism that all browsers use.
    If you modify twice a specific style property before a reflow happens, only the second modification will be taken into account.
    No need to use a setTimeout to fix that, you can just *trigger* a reflow between the two style modification:

    elem.style.top = “10px”;
    window.getComputedStyle(elem).getPropertyValue(“top”);
    elem.style.top = “100px”;

    Lr

    louisremi

    22 Jul 11 at 4:06 am

  7. @louisremi

    Once again?

    Isnt triggering an additional reflow relatively expensive?

    mike

    mikechambers

    22 Jul 11 at 10:03 am

  8. in regards to the detection of finished rendering of inserted DOM elements:
    we found out the best approach would be to reset a style property of the node before triggering the css transform -

    domObject.style.display = document.defaultView.getComputedStyle(domObject)['display'];

    that helps without the need to care about timers etc

    as

    12 Dec 11 at 1:11 am

Leave a Reply

Follow

Get every new post on this blog delivered to your Inbox.

Join other followers: