I'm trying to implement a FLIP animation to see if I understand it correctly.
In this codepen (please forgive the bad code, I'm just messing around), if I comment out the sleep, the smooth transition no longer works. The div changes position suddenly. This is strange because the sleep time is 0ms.
import React, { useRef, useState } from "https://esm.sh/react@18"; import ReactDOM from "https://esm.sh/react-dom@18"; let first = {} let second = {} const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const App = () => { const [start, setStart] = useState(true); const boxRefCb = async el => { if (!el) return; el.style.transition = ""; const x = parseInt(el?.getBoundingClientRect().x, 10); const y = parseInt(el?.getBoundingClientRect().y, 10); first = { x: second.x, y: second.y }; second = { x, y }; const dx = first.x - second.x; const dy = first.y - second.y; const transStr = `translate(${dx}px, ${dy}px)`; el.style.transform = transStr; await sleep(0); // comment me out el.style.transition = "transform .5s"; el.style.transform = ""; } return ( <> <div style={{ display: "flex", gap: "1rem", padding: "3rem"}}> <div ref={ start ? boxRefCb : null } style={{ visibility: start ? "" : "hidden", width: 100, height: 100, border: "solid 1px grey" }}></div> <div ref={ !start ? boxRefCb : null } style={{ visibility: !start ? "" : "hidden", width: 100, height: 100, border: "solid 1px grey" }}></div> </div> <button style={{ marginLeft: "3rem"}} onClick={() => setStart(start => !start)}>start | {start.toString()}</button> </> ); } ReactDOM.render(<App />, document.getElementById("root"))
I suspect this is some event loop magic that I don't understand. Can someone explain this to me?
You are using a normal JavaScript solution to this problem, but React uses a virtual DOM and expects DOM elements to be re-rendered when state changes. Therefore, I recommend leveraging React state to update the XY position of elements in the virtual DOM, but still using CSS.
Working DemoHere Or the code can be found here:
During
sleep
, the browser may have time to recalculate the CSSOM box (also known as "execution reflow"). Without it, yourtransform
rules won't actually apply.In fact, the browser will wait until it's really needed to apply your changes and update the entire page box model, because doing so can be very expensive.
When you do something like
All CSSOMs will see is the latest status,
"green"
. The other two were discarded.So in your code, when you don't let the event loop actually loop, you will never see the
transStr
value either.However, relying on 0ms
setTimeout
is a problem call, there is nothing to ensure that the style is recalculated at that time. Instead, it's better to force a recalculation manually. Some DOM methods/properties do this synchronously. But keep in mind that reflow can be a very expensive operation, so be sure to use it occasionally, and if there are multiple places in your code that require this operation, be sure to connect them all so that a single reflow can be performed. p>