Trying to trigger a CSS transition using JavaScript
Asked Answered
C

4

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 ?

Cove answered 4/3, 2016 at 20:0 Comment(10)
What's the point of using setTimeout? It isn't doing anything. Maybe try removing them and see what happens?Merrilee
Yes it is useful, it queues the code removing the css class after that the browser has first rendered the DOMCove
Try removing the backtick where you inserted the inner HTML elements in your var template. and see instead add double quotes ""Ottoman
oooh... tha backtick doesn't do anything... you should probably be using double quotes (") for the outside and single quotes (') for the class=Merrilee
The backtick corresponds to an ES6 template string.Cove
ES6 is not fully supported in browsers. Are you sure the ES6 features you are using are supported in each browser?Clementia
Template strings are ok of you use modern browsers, see kangax.github.io/compat-table/es6Cove
Template strings work in all of the browser's he's targeting.Grantor
I've created a Fiddle if anyone needs one: jsfiddle.net/e3grdg4m. I'm also not able to reproduce the issue in Chrome: it works correctly there.Grantor
Don't use fiddles or so. I works on fiddle's but not outside.Cove
C
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);
Cove answered 4/3, 2016 at 21:6 Comment(0)
T
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>
Top answered 18/12, 2021 at 19:0 Comment(0)
N
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>
Nita answered 4/3, 2016 at 20:23 Comment(4)
Try it outside fiddle (or alike), directly inside a browser, it doesn't work for me. But it works inside fiddle, codepen, etc...Cove
That's not true, the first animation is triggered.Cove
Your code does not seem to answer my question about why does it work for the red div but not for the blue div ?Cove
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.Nita
V
-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>
Viscardi answered 4/3, 2016 at 20:19 Comment(8)
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.Cove
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.Cove
Yes code runs on document Load, tested in IE11, Firefox, Chrome and Chrome CanaryViscardi
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). :-(Cove
try to wrap your code in an event listener onload = function() { //code } }Viscardi
No effect on my browsers. Why would that change something anyway ? For the blue div I mean.Cove
It's unusual you don't see the transition, have you tried a cross browser test in virtual browsers? like browserstack.com?Viscardi
I've added an edit to my question. Found that there is a slight delay that the browsers need to recompute the style differences. Maybe it's the real culprit ? Because it looks like something doesn't (or sometimes doesn't ) have enough time to notice that the opacity property changed. If I give it a little more time (say 1/60s), that works consistently.Cove

© 2022 - 2024 — McMap. All rights reserved.