joews joews - 22 days ago 11
CSS Question

Trigger CSS transition on appended element

As this question observes, immediate CSS transitions on newly-appended elements are somehow ignored - the end state of the transition is rendered immediately.

For example, given this CSS (prefixes omitted here):

.box {
opacity: 0;
transition: all 2s;
background-color: red;
height: 100px;
width: 100px;
}

.box.in { opacity: 1; }


The opacity of this element will be set immediately to 1:

// Does not animate
var $a = $('<div>')
.addClass('box a')
.appendTo('#wrapper');
$a.addClass('in');


I have seen several ways of triggering the transition to get the expected behaviour:

// Does animate
var $b = $('<div>')
.addClass('box b')
.appendTo('#wrapper');

setTimeout(function() {
$('.b').addClass('in');
},0);

// Does animate
var $c = $('<div>')
.addClass('box c')
.appendTo('#wrapper');

$c[0]. offsetWidth = $c[0].offsetWidth
$c.addClass('in');

// Does animate
var $d = $('<div>')
.addClass('box d')
.appendTo('#wrapper');
$d.focus().addClass('in');


The same methods apply to vanilla JS DOM manipulation - this is not jQuery-specific behaviour.

Edit - I am using Chrome 35.

JSFiddle (includes vanilla JS example).


  • Why are immediate CSS animations on appended elements ignored?

  • How and why do these methods work?

  • Are there other ways of doing it

  • Which, if any, is the preferred solution?


Answer

The cause of not animating the newly added element is batching reflows by browsers.

When element is added, reflow is needed. The same applies to adding the class. However when you do both in single javascript round, browser takes its chance to optimize out the first one. In that case, there is only single (initial and final at the same time) style value, so no transition is going to happen.

The setTimeout trick works, because it delays the class addition to another javascript round, so there are two values present to the rendering engine, that needs to be calculated, as there is point in time, when the first one is presented to the user.

There is another exception of the batching rule. Browser need to calculate the immediate value, if you are trying to access it. One of these values is offsetWidth. When you are accessing it, the reflow is triggered. Another one is done separately during the actual display. Again, we have two different style values, so we can interpolate them in time.

This is really one of very few occasion, when this behaviour is desirable. Most of the time accessing the reflow-causing properties in between DOM modifications can cause serious slowdown.

The preferred solution may vary from person to person, but for me, the access of offsetWidth (or getComputedStyle()) is the best. There are cases, when setTimeout is fired without styles recalculation in between. This is rare case, mostly on loaded sites, but it happens. Then you won't get your animation. By accessing any calculated style, you are forcing the browser to actually calculate it.