我正在尝试实现 FLIP 动画,看看我是否理解正确。
在这个代码笔中(请原谅糟糕的代码,我只是在乱搞),如果我注释掉睡眠,平滑过渡将不再有效。 div 突然改变位置。这很奇怪,因为睡眠时间为 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"))
我怀疑这是一些我不理解的事件循环魔法。有人可以帮我解释一下吗?
您正在使用普通的 JavaScript 解决方案来解决这个问题,但 React 使用虚拟 DOM,并期望在状态更改时重新渲染 DOM 元素。因此,我建议利用 React 状态来更新虚拟 DOM 中元素的 XY 位置,但同时仍使用 CSS。
工作演示此处或可以在此处找到代码:
在
sleep
期间,浏览器可能有时间重新计算 CSSOM 框(也称为“执行回流”)。没有它,您的transform
规则就不会真正应用。事实上,浏览器会等到真正需要时才应用您所做的更改,并更新整个页面框模型,因为这样做可能会非常昂贵。
当你做类似的事情
所有CSSOM将看到的是最新状态,
“绿色”
。另外两个就被丢弃了。因此,在您的代码中,当您不让事件循环实际循环时,也永远不会看到
transStr
值。但是,依赖 0ms
setTimeout
是一个问题调用,没有任何东西可以确保样式在那时重新计算。相反,最好手动强制重新计算。一些 DOM 方法/属性 会同步执行此操作。但请记住,回流可能是一项非常昂贵的操作,因此请务必偶尔使用它,如果代码中有多个位置需要此操作,请务必将它们全部连接起来,以便执行单次回流。 p>