38

I cannot find a way to use css transitions on newly created dom elements.

let's say I have an empty html document.

<body>
    <p><a href="#" onclick="return f();">click</a></p>
</body>

I also have this css

#id {
    -moz-transition-property: opacity;
    -moz-transition-duration: 5s;
    opacity: 0;
}

#id.class {
    opacity: 1;
}

and this js

function f() {
    var a = document.createElement('a');
    a.id = 'id';
    a.text = ' fading in?';
    document.getElementsByTagName('p')[0].appendChild(a);
    // at this point I expect the span element to be with opacity=0

    a.className = 'class';
    // now I expect the css transition to go and "fade in" the a        

    return false;
}

but, as you can see on http://jsfiddle.net/gwxkW/1/ when you click the element appears instantaneously.

If I try to set the class in a timeout() i often find the result, but to me it seems more a race between javascript and the css engine. Is there some specific event to listen? I tried to use document.body.addEventListener('DOMNodeInserted', ...) but it's not working.

How can I apply css transitions on newly created elements?

Vito De Tullio
  • 2,273
  • 3
  • 29
  • 47
  • Setting the class name with a `setTimeout` works, but only if the delay is 6ms or more for me. Not sure of a nice way. – pimvdb Aug 23 '12 at 09:47
  • yeah, I tried some low milliseconds value (range 0-10) and most of times it works with 5-6 ms, but I *feel* it's the wrong approach. What I hoped is some sort of event related to "from this moment the css are applied" but I don't know if they exist – Vito De Tullio Aug 23 '12 at 09:53
  • See http://stackoverflow.com/questions/18564942/clean-way-to-programmatically-use-css-transitions-from-js for the discussion of a clean way to do this. – Nickolay Aug 06 '15 at 17:04
  • Here is the jQuery equivalent of the same question (with a good answer): https://stackoverflow.com/a/12814986/339440 – Stephen R Sep 19 '19 at 21:58

3 Answers3

40

In Firefox, it does appear to be a race between layout completing and the CSS transition. Chrome is much more predictable. If I set the class name on a setTimeout(), Chrome always works, Firefox only works if the setTimeout() time is long.

With this code in Firefox (even using the setTimeout()), the text shows immediately:

function f() {
    var a = document.createElement('a');
    a.id = 'id';
    a.innerHTML = ' fading in?';
    document.getElementsByTagName('p')[0].appendChild(a);
    // at this point I expect the span element to be with opacity=0

    setTimeout(function() {
        a.className = 'fadeIn';
    }, 10);
    return false;
}

But, if I force a reflow by requesting a property that can only be returned after layout, it then starts to work in Firefox:

function f() {
    var a = document.createElement('a');
    a.id = 'id';
    a.innerHTML = ' fading in?';
    document.getElementsByTagName('p')[0].appendChild(a);
    // at this point I expect the span element to be with opacity=0

    // request property that requires layout to force a layout
    var x = a.clientHeight;
    setTimeout(function() {
        a.className = 'fadeIn';
    }, 10);
    return false;
}

Furthermore, once I've request that property to force a layout, I can even remove the setTimeout() and the animation works in Firefox.

function f() {
    var a = document.createElement('a');
    a.id = 'id';
    a.innerHTML = ' fading in?';
    document.getElementsByTagName('p')[0].appendChild(a);
    // at this point I expect the span element to be with opacity=0

    // request property that requires layout to force a layout
    var x = a.clientHeight;
    a.className = 'fadeIn';
    return false;
}

You can see this last one work here in both Chrome and Firefox: http://jsfiddle.net/jfriend00/phTdt/

And, here's an article that discusses the phenomenon: http://gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-webkit.html

jfriend00
  • 580,699
  • 78
  • 809
  • 825
16

I found a nicer way to trigger layout and make transitions work just after appending the element to the DOM:

window.getComputedStyle(element).opacity;
timaschew
  • 15,034
  • 4
  • 56
  • 73
demian85
  • 2,124
  • 2
  • 18
  • 19
  • 1
    That solution is also discussed at the bottom of this article by Tim Taubert: https://timtaubert.de/blog/2012/09/css-transitions-for-dynamically-created-dom-elements/ – robocat Apr 22 '15 at 03:01
  • 1
    I wouldn't use `.cssText` though, as it's quite heavy (serializes all the styles). – Nickolay Aug 06 '15 at 16:58
  • just use `.opacity` instead, this should be used anyway, because this is the style which should be finished by the render engine – timaschew Oct 11 '15 at 10:24
  • updated for linkrot: http://web.archive.org/web/20160425095216/https://timtaubert.de/blog/2012/09/css-transitions-for-dynamically-created-dom-elements/ – albert Dec 26 '20 at 17:04
8

requestAnimationFrame() (https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame) appears to work across Firefox, Chrome and Safari. A more reliable, logical solution that setTimeout(). For older browsers (IE8), it will require a Polyfill (naturally, the transition won't occur, but the CSS will still change).

Jason
  • 3,919
  • 4
  • 19
  • 30