1

I want to add a class to a div, in an animated way!

const divnew = document.createElement("div");
    divnew.classList.add("circle");
    const beans = document.getElementById("beans");
    beans.append(divnew);
    divnew.classList.add("up");
.circle{
    background:#FCD299;
    border-radius:50%;
    width:50px;
    height:50px;
    position:relative;
    top:300px;
    left: 500px;
    transition:all 300ms;
}

.circle.up{
    position:relative;
    top:200px;
    left: 500px;
}
<div id="beans"></div>

as you can see, it adds a class to a div, and then i want it to add the class up in an animation way. But it doesnt do that. help

Robin Zigmond
  • 17,805
  • 2
  • 23
  • 34
Cookie Man
  • 21
  • 3
  • Does this answer your question? [CSS transitions do not work when assigned trough JavaScript](https://stackoverflow.com/questions/8210560/css-transitions-do-not-work-when-assigned-trough-javascript) – mrtechtroid Apr 18 '22 at 10:34

2 Answers2

0

The browser first evaluates all the javascript, and afterwards does the CSS stuff (probably glossing over some details, but that's the basic gist). So the browser just sees a div with both the circle and up class. To fix it, you can delay setting the up class, using setTimeout like so:

const divnew = document.createElement("div");
divnew.classList.add("circle");
const beans = document.getElementById("beans");
beans.append(divnew);

setTimeout(() => {
    divnew.classList.add("up")
}, 0)

Even if the timeout is set to 0ms, it gets pushed to the microtask queue, which is evaluated later and thus the CSS gets updated and the browser animates the element. So you're getting the animation but it still happens instantly after creating the div.

Odilf
  • 1,185
  • 7
  • 12
  • A `setTimeout` with `0` delay will not create a microtask, in fact- if it did it wouldn't achieve the desired effect here, since all microtasks are executed immediately after the current macro task finishes, without letting anything else happen (such as rendering elements or processing DOM), if you replace your `setTimeout` with a `queueMicrotask` you'll see. `setTimeout` `0` is acceptable here- but the correct function to use is `requestAnimationFrame` which will queue the callback after style and layout and directly before the next paint (ensuring the animation starts as soon as possible) – Qwerasd Apr 18 '22 at 11:11
  • I never really understood how`requestAnimationFrame` worked and I just knew that `setTimeout` did work. But you're right that I should be using the correct function. I'll try to use it more, thanks! – Odilf Apr 20 '22 at 14:22
0

You need to use requestAnimationFrame in order to allow the element to be rendered without the destination class first, otherwise it doesn't know it needs to transition because the first time it's rendered it has no 'memory' of not having the class.

const div = document.createElement("div");
div.classList.add("circle");

document.body.appendChild(div);

window.requestAnimationFrame(
    () => div.classList.add("animate")
);
.circle {
    background: #FCD299;
    border-radius: 50%;
    width: 50px;
    height: 50px;
    position: absolute;
    top: 0px;
    left: 0px;
    transition: all 3000ms;
}

.circle.animate {
    top: calc(100% - 50px);
    left: calc(100% - 50px);
}

Alternatively you can instead use a CSS animation and define @keyframes for your desired movement rather than relying on a transition -- this also avoids any need for JavaScript.

.circle {
    background: #FCD299;
    border-radius: 50%;
    width: 50px;
    height: 50px;
    position: absolute;
    animation: 3000ms forwards animate;
}

@keyframes animate {
    from {
        top: 0;
        left: 0;
    }
    
    to {
        top: calc(100% - 50px);
        left: calc(100% - 50px);
    }
}
<div class="circle"></div>

(note that the animation-fill-mode is forwards, it will maintain the final value of the animated attributes, rather than just snapping back to unset when the animation finishes)

Qwerasd
  • 369
  • 1
  • 5