6

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 ?

10
  • What's the point of using setTimeout? It isn't doing anything. Maybe try removing them and see what happens? Commented Mar 4, 2016 at 20:07
  • Yes it is useful, it queues the code removing the css class after that the browser has first rendered the DOM Commented Mar 4, 2016 at 20:09
  • Try removing the backtick where you inserted the inner HTML elements in your var template. and see instead add double quotes "" Commented Mar 4, 2016 at 20:11
  • oooh... tha backtick doesn't do anything... you should probably be using double quotes (") for the outside and single quotes (') for the class= Commented Mar 4, 2016 at 20:12
  • 1
    The backtick corresponds to an ES6 template string. Commented Mar 4, 2016 at 20:13

4 Answers 4

3

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);
Sign up to request clarification or add additional context in comments.

Comments

1

To trigger a transition you don't really need a class to toggle. This is important since you may not be able to set up classes dynamically to toggle beforehand. In other words you may just need to alter some CSS attributes to trigger the transitions. However yes... you need to satisfy the followings;

  1. In order your <div id="blue"></div> element to animate, it must carry the std class where the transition is defined. So first we should do like blue.classList.add("std");
  2. You need to perform the task asynchronously. Refreshing the DOM asynchronously is best done by the requestAnimationFrame() function.

var blue = document.getElementById("blue");
blue.classList.add("std");
blue.style.backgroundColor = "blue";
blue.style.opacity         = 0;

document.querySelector('button')
        .addEventListener( 'click'
                         , _ => requestAnimationFrame(_ => blue.style.opacity = 1)
                         );
.std { width:100px;
       height:100px;
       opacity: 1;
       transition: opacity 5s;
}
.std--red { background-color: red;}
<button>click here</button>
<div class='std std--red'></div>
<div id="blue"></div>

Comments

0

It seems you have to trigger the transition by making a reference to the style. I simply added this line (even into console.log is ok)

document.querySelector('.std.std--blue').style.width = '20';

and here it works: jsfiddle

NB: I am using FirefoxDeveloperEdition.

I have followed this solution:

CSS Transitions do not animate when you add or remove class, It will only animate when you change the CSS properties.

Complete script

<script>

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;
    document.querySelector('.std.std--blue').style.width = '20';
    setTimeout(function() {

        document.querySelector('.std.std--blue').style.width = '100';
        document.querySelector('.std--blue').classList.toggle('std--hidden');
    }, 0);

    });
</script>

4 Comments

Try it outside fiddle (or alike), directly inside a browser, it doesn't work for me. But it works inside fiddle, codepen, etc...
That's not true, the first animation is triggered.
Your code does not seem to answer my question about why does it work for the red div but not for the blue div ?
CSS Transitions do not animate when you add or remove class, It will only animate when you change the CSS properties. I added it also in the answer.
-1

You must set a value to the timeout so you give time for the element to be inserted in the DOM (must exist before calling document.querySelector)

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

you don't need the first timeout

onload = function() {
document.querySelector('.std--red').classList.toggle('std--hidden');

 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() { document.querySelector('.std--blue').classList.toggle('std--hidden')},100);
    });
}
 .std {
            width:100px;
            height:100px;
            opacity: 1;
            transition: opacity 5s;
        }

        .std--hidden {
            opacity: 0;
        }

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

        .std--blue {
            background-color: blue;
        }
<button>click here</button>
<div class='std std--red std--hidden'></div>
<div class='insert-here'>
</div>

8 Comments

Sorry I can't accept your answer as it does not answer the question. I know that I can change the delay, I just don't understand why it doesn't work in the second case.
If you try running the code outside of jsfiddle (open the HTML inside a browser), do you see the first animation if you don't place a setTimeout ? I don't.
Yes code runs on document Load, tested in IE11, Firefox, Chrome and Chrome Canary
On my computer it works under Safari Version 9.0.3 (11601.4.4) but is working randomly under Chrome Version 48.0.2564.116 (64-bit) and doesn't work under Firefox Dev Edition 46.0a2 (2016-03-04). :-(
try to wrap your code in an event listener onload = function() { //code } }
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.