0

So in this example, if I don't nest the

box.style.animation = 'myanimation 3s'

inside of the setTimeout, then the code doesn't work. But when I run it like this, with a timeout delay of ZERO, it works. Originally I thought that maybe it was an execution timing error, so I set the Timeout delay to 50, but I got curious and tried lower amounts until I got to zero, and it still ran. Essentially I was just trying to implement an animation every time the element is clicked. Is there a better/safer workaround for this? Also, for curiosity, what is the difference between running that line of code directly after the 'if' statement versus nesting it in a setTimeout with a delay of 0? I'm using Firefox with Ubuntu LTS 20.04

edit: I should add, if I don't nest inside the setTimeout function, the animation will run the first time, but not any subsequent time. But when I run the code as shown, it runs everytime.enter image description here

const box = document.querySelector('.box')

box.addEventListener('click', function() {
    if(box.style.animation){
        box.style.animation = ''
    }
    setTimeout(function() {
        box.style.animation = 'myanimation 3s'
    }, 0)



})

EDIT: Another answer I came across on reddit, is to listen for an animationend event, such as:

const box = document.querySelector('.box')

box.addEventListener('click', function() {
    box.style.animation = 'myanimation 3s'
    })

box.addEventListener('animationend', function()  {
    box.style.animation = ''
})
rgbellotti
  • 19
  • 6
  • 1
    Can you please post the code as text, and not as an image? That makes it easier for others to help, and also makes the question accessible to users using a screen reader! – Constantin Groß Aug 04 '21 at 06:34
  • [Please do not upload images of code/errors when asking a question.](//meta.stackoverflow.com/q/285551) – Reyno Aug 04 '21 at 06:36
  • @CBroe Yes that does offer some other options, though I'm unclear on the differences between the varying approaches – rgbellotti Aug 04 '21 at 06:48

1 Answers1

0

The reason why it seems like nothing is happening when you don't use the timeout is that modern browsers optimize away the first statement completely, in order to prevent unnecessary operations from happening that would have a negative effect on performance.

The browser notices that you set the animation property to an empty string, just to set it to another value in the line below. That would cause a reflow, and in many cases that wouldn't be what you want. In this case, trying to reset a CSS animation, it is exactly what you want, though.

So you need to tell the browser to intentionally cause a reflow. One way to do that is using setTimeout, so the first expression will not be optimized away. Another way is to cause reflow by doing something that will itself inherently cause a reflow. offsetLeft, offsetTop, offsetWidth and offsetHeight are such a properties that cause a reflow in order to report back accurate values when they are accessed. It's enough to simply access them, without putting the values into a variable or anything:

const box = document.querySelector('.box')
box.addEventListener('click', function() {
  if (box.style.animation) {
    box.style.animation = ''
  }
  box.offsetLeft; // this forces a reflow, just like setTimeout
  box.style.animation = 'myanimation 3s'
})
.box {
  background: red;
  width: 120px;
  height: 120px;
  position: relative;
}

.box::after {
  content: "click me!";
  color: #fff;
  display: block;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%)
}

@keyframes myanimation {
  from {
    transform: translateY(0)
  }
  50% {
    transform: translateY(1em)
  }
  to {
    transform: translateY(0);
  }
}
<div class="box">

</div>
Constantin Groß
  • 10,719
  • 4
  • 24
  • 50
  • Thank you, that's what I was trying to figure out, and since setTimeout() is a way to cause a reflow then I'll just keep the code as is. I appreciate the input! – rgbellotti Aug 04 '21 at 06:55