我正在嘗試實作 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>