javascriptcssgoogle-chromecss-transitionsclonenode

Chrome on OSX: CSS transitions applied immediately to second clone of div


I want to do the following: Create a div (#test) and then clone that div many times, each time it is cloned adding a css transition to it via javascript. All works well the first time, but if I try to clone a second time, and apply the css transition, then the transition doesn't work.

In this example (https://jsfiddle.net/9uL1qt6n/13/), the red square moves like it is supposed to, but the green square doesn't move, and appears at the end of the transition instantly.

Here is the javascript code that I am using:

function move(color){
  let clone=document.getElementById("test").cloneNode(true);
  clone.id=color;
  clone.style.display="block";
  clone.style.backgroundColor=color;
  document.getElementById("main").prepend(clone);
  setTimeout(function(){
      clone.style.left="500px";
  },0)
}

setTimeout(function(){move("red")},500);
setTimeout(function(){move("green")},750);

I am expecting the red square to start with left=0px at .5s and move to the right, and then a green square that starts with left=0px at .75s and move to the right. What I am seeing is a red square that starts with left=0px at .5s and moves to the right, and then a green square that starts with left=500px at .75s and does not move.

Edit: This appears to work correctly on Safari on Mac, as well as on Safari and Chrome in iOS. The above suggested behavior only appears on Chrome on Mac.


Solution

  • This is because setTimeout(/**/, 0) does not guarantee that the callback will be executed on a subsequent frame. Which could (depending on browser implementation and computer speed) result in the style being applied on the same frame as the node being inserted into the DOM.

    In theory, you should use requestAnimationFrame instead, which is exactly meant for this type of situations.

    However, in the fiddle you linked, it only worked if I doubled the requestAnimationFrame which is imperceptible but still undesirable... IDK if it's a fluke of JSFiddle or what...

    function move(color){
      let clone=document.getElementById("test").cloneNode(true);
      clone.id=color;
      clone.style.display="block";
      clone.style.backgroundColor=color;
      document.getElementById("main").prepend(clone);
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
            clone.style.left="500px";
        })
      })
    }
    

    here's a snippet: I find the same thing in the SO snippet

    function move(color) {
      let clone = document.getElementById("test").cloneNode(true);
      clone.id = color;
      clone.style.display = "block";
      clone.style.backgroundColor = color;
      document.getElementById("main").prepend(clone);
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          clone.style.left = "500px";
        })
      })
    }
    
    
    setTimeout(() => move("red"), 500);
    setTimeout(() => move("green"), 750);
    #main {
      display: block;
      width: 100vw;
      height: 100vh;
      background-color: blue;
    }
    
    .test {
      position: absolute;
      display: none;
      width: 100px;
      height: 100px;
      background-color: red;
      transition: left 1s ease;
      transform: scale(1);
      left: 0px;
    }
    <div id="main"></div>
    <div id="test" class="test"></div>