Search code examples
javascriptcsslistenercss-transitionsinnerhtml

Trying to trigger a CSS transition using JavaScript


Playing with JavaScript and CSS transition, I tried to remove a CSS class right after having dynamically inserted a div, using JavaScript and innerHTML.

I'm really surprised to see that the CSS transition associated with the opacity of the blue div is not triggered the way I want (works under Safari, works randomly under Chrome, doesn't work under Firefox Dev Edition). Can someone explain this phenomenon ?

I'm not sure about why it is not working the same way as it does for the red div. Maybe something I don't know about how browsers handle innerHTML ?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Having fun with JS</title>
    <style>
        .std {
            width:100px;
            height:100px;
            opacity: 1;
            transition: opacity 5s;
        }

        .std--hidden {
            opacity: 0;
        }

        .std--red {
            background-color: red;
        }

        .std--blue {
            background-color: blue;
        }
    </style>
</head>
<body>
<button>click here</button>
<div class='std std--red std--hidden'></div>
<div class='insert-here'>
</div>
<script>
    // let a chance to the browser to trigger a rendering event before the class is toggled
    // see http://stackoverflow.com/a/4575011
    setTimeout(function() {
        // everything works fine for the red div
        document.querySelector('.std--red').classList.toggle('std--hidden');
    }, 0);


    document.querySelector('button').addEventListener('click', function(e) {

        var template = `<div class='std std--blue std--hidden'></div>`;
        document.querySelector('.insert-here').innerHTML = template;

        setTimeout(function() {
            // Why does the CSS transition seems to be triggered randomly ?
            // well more exactly
            // - it works under my Safari
            // - it does not work under my FirefoxDeveloperEdition
            // - it works randomly under my Google Chrome
            document.querySelector('.std--blue').classList.toggle('std--hidden');
        }, 0);

    });
</script>
</body>
</html>

EDIT

So I've just read the CSS transitions specs and found this

This processing of a set of simultaneous style changes is called a style change event. (Implementations typically have a style change event to correspond with their desired screen refresh rate, and when up-to-date computed style or layout information is needed for a script API that depends on it.)

Can this be the explanation somehow ? Does the setTimeout 0 is too fast on some browsers that they don't have time to compute the style differences and thus don't trigger a style change event ? Indeed if use a longer setTimeout (say, ~16.6666, guessing a 1/60 refresh rate...) it seems to work everywhere. Can someone confirm that ?


Solution

  • I think I've found the answer, see the CSS 3 transition spec:

    Since this specification does not define when a style change event occurs, and thus what changes to computed values are considered simultaneous, authors should be aware that changing any of the transition properties a small amount of time after making a change that might transition can result in behavior that varies between implementations, since the changes might be considered simultaneous in some implementations but not others.

    I tried to add a little delay to let the browsers notice the style differences and it works consistently. It seems that some browsers are executing a setTimeout 0 really faster than others :-)

        setTimeout(function() {
            document.querySelector('.std--blue').classList.toggle('std--hidden');
        }, 17);