最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Transition not working without setTimeout in useEffect - Stack Overflow

programmeradmin4浏览0评论

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
Add a ment  | 

5 Answers 5

Reset to default 3 +25

I 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>

发布评论

评论列表(0)

  1. 暂无评论