admin管理员组文章数量:1321605
I have one small ponent, with some text.
In useEffect, I am simply setting the top of the element to some values.
CSS:
.box {
display: flex;
flex-direction: column;
position: relative;
top: 0;
transition: top 0 ease-in;
}
useEffect(() => {
setTimeout(()=> { // with this it is working.
const elems = document.getElementsByClassName("box");
[...elems].forEach(function (e) {
e.style.top =`-200px`; // without settimeout transition is not working
});
});
}, []);
My Markup:
<div className='progress'>
<div className='box' />
<div className='box' />
<div className='box' />
</div>
Now when I do frequent refreshes, I see the transition happening a few times, but not every time.
But when I wrap my code in setTimeout I see a transition every time.
I am not sure why is this happening.
I have one small ponent, with some text.
In useEffect, I am simply setting the top of the element to some values.
CSS:
.box {
display: flex;
flex-direction: column;
position: relative;
top: 0;
transition: top 0 ease-in;
}
useEffect(() => {
setTimeout(()=> { // with this it is working.
const elems = document.getElementsByClassName("box");
[...elems].forEach(function (e) {
e.style.top =`-200px`; // without settimeout transition is not working
});
});
}, []);
My Markup:
<div className='progress'>
<div className='box' />
<div className='box' />
<div className='box' />
</div>
Now when I do frequent refreshes, I see the transition happening a few times, but not every time.
But when I wrap my code in setTimeout I see a transition every time.
I am not sure why is this happening.
Share Improve this question edited Nov 30, 2023 at 17:36 Rahul asked Nov 30, 2023 at 16:59 RahulRahul 5,8547 gold badges43 silver badges94 bronze badges 1-
So your code has the styles being applied once, on page load. Why aren't you using standard styling practices for this, or better yet styles on your
box
CSS class? – isherwood Commented Nov 30, 2023 at 17:39
5 Answers
Reset to default 3 +25I have no idea about React but what you are trying to do is relatively striaghtforward with @keyframes
. There is no need to use setTimeout()
. Here is a snippet that you can modify:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS Animation</title>
<style>
@keyframes moveMeDown {
from {
top: -200px; /* Start position */
}
to {
top: 0; /* End position */
}
}
.box {
display: flex;
flex-direction: column;
position: relative;
top: 0;
animation: moveMeDown 0.7s ease-in; /* Set the name, duration and effect */
}
</style>
</head>
<body>
<section>
<p>Just a test</p>
<div class='progress'>
<div class='box'>One</div>
<div class='box'>Two</div>
<div class='box'>Three</div>
</div>
</section>
</body>
</html>
You just need to change useEffect()
to useLayoutEffect()
, this is the same as useEffect, but runs after the ponent Virtual DOM is updated, but before the change is painted to the screen.
This hook is designed to cover the use case in your question.
https://react.dev/reference/react/useLayoutEffect
The browser might not have time to apply the initial state before transitioning to the new state. The setTimeout introduces a delay, giving the browser time to apply the initial state and then transition to the new state.
import React, { useEffect } from 'react';
const YourComponent = () => {
useEffect(() => {
setTimeout(() => {
const elems = document.getElementsByClassName("box");
[...elems].forEach(function (e) {
e.style.top = "-200px";
});
}, 0);
}, []);
return (
<div className="box">
{/* Your ponent content */}
</div>
);
};
export default YourComponent;
Event Loop
JavaScript has a runtime model based on an event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks. This model is quite different from models in other languages like C and Java.
The reason why setTimeout()
works every time, is because it forces the code inside the setTimeout()
to run on the next loop. In React, useEffect
only runs on the client, but it's municating with React's internal v-dom.
useEffect Docs
Effects only run on the client. They don’t run during server rendering.
React will generally let the browser paint the updated screen first before running your Effect.
Even if your Effect was caused by an interaction (like a click), the browser may repaint the screen before processing the state updates inside your Effect.
To summarize
When React renders the page, it will process the ponent, and when it's client side run the useEffect
. It may run the useEffect
before the browser has fully finished rendering, which would mean that your .box
would have the top: -200px
in the initial render.
Using setTimeout
even for 1ms, will force the code to run on the next event loop cycle, and thus ensure that the browser fully renders before adding the top: -200px
which would all CSS to calculate the transition, and display it to the user.
I would remend that you use the [requestAnimationFrame()][3]
method instead of setTimeout()
since this method is designed to do what you're looking for and will provide better performance. Although, in this case there won't really be much of a difference at all.
The window.requestAnimationFrame() method tells the browser you wish to perform an animation. It requests the browser to call a user-supplied callback function before the next repaint.
As others have pointed out, when you call useEffect
or similar functions that are run every render cycle, the call might fall in between 2 repaint cycles of the browser (which is usually max 60 times per second), the browser will say 'start and end states are both -200px, so no need to transition'.
So usually the trick is to trigger reflow/repaint of the browser via layout trashing or calling functions like getBoundingClientRect
or getComputedStyle
or setTimeout
to PREVENT the function call that runs the transition to fall into the same repaint bucket.
The solutions posted here are correct, I just want to bring another approach using Element.animate
. You can essentially use this approach within useEffect
or people that don't use react
can also resort to it.
The functionality boils down to this:
ch(node)
.cancelAnimate()
.immediateAnimate({top: "0px"}) //not necessary, but more declerative
.animate({top: "-200px"}, {duration: 600, fill: "both", delay: i * 300})
This will be run randomly before the entire animation is done, and each time you should see the pink boxes restart again. I am using an external library but you can use anything you want.
function runMe() {
ch(document.querySelector(".progress"))
.each((node, i) => ch(node).cancelAnimate().immediateAnimate({
top: "0px"
}).animate({top: "-200px"}, {duration: 600, fill: "both", delay: i * 300})
)
}
function wait(t){
return new Promise(res => setTimeout(res, t))
}
async function runRandomly(){
await wait(Math.random() * 1000);
runMe();
requestAnimationFrame(runRandomly)
}
requestAnimationFrame(runRandomly);
.box {
display: flex;
flex-direction: column;
position: relative;
top: 0;
transition: top 0 ease-in;
width: 50px;
height: 50px;
background: DeepPink;
margin: 5px 5px;
}
<script src="https://cdn.jsdelivr/npm/[email protected]/dist/cahir.0.0.6.evergreen.umd.min.js"></script>
<script src="https://cdn.jsdelivr/npm/[email protected]/collections/DOM/ch.js"></script>
<div class='progress'>
<div class='box'></div>
<div class='box'></div>
<div class='box'></div>
</div>
本文标签: javascriptTransition not working without setTimeout in useEffectStack Overflow
版权声明:本文标题:javascript - Transition not working without setTimeout in useEffect - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742103351a2420905.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论