Heim > Fragen und Antworten > Hauptteil
Ich versuche, die FLIP-Animation zu implementieren, um zu sehen, ob ich sie richtig verstehe.
Wenn ich in diesem Codepen (entschuldigen Sie den schlechten Code, ich mache nur herum) den Ruhezustand auskommentiere, funktioniert der reibungslose Übergang nicht mehr. Der Div ändert plötzlich seine Position. Das ist seltsam, da die Ruhezeit 0 ms beträgt.
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"))
Ich vermute, dass es sich hierbei um eine Ereignisschleifenmagie handelt, die ich nicht verstehe. Kann mir das jemand erklären?
P粉0222857682024-04-07 14:25:14
您正在使用普通的 JavaScript 解决方案来解决这个问题,但 React 使用虚拟 DOM,并期望在状态更改时重新渲染 DOM 元素。因此,我建议利用 React 状态来更新虚拟 DOM 中元素的 XY 位置,但同时仍使用 CSS。
工作演示此处或可以在此处找到代码:
import { useState, useRef, useLayoutEffect } from "react"; import "./styles.css"; type BoxXYPosition = { x: number; y: number }; export default function App() { const startBox = useRef(null); const startBoxPosition = useRef ({ x: 0, y: 0 }); const endBox = useRef (null); const [boxPosition, setBoxPosition] = useState ({ x: 0, y: 0 }); const { x, y } = boxPosition; const hasMoved = Boolean(x || y); const updatePosition = () => { if (!endBox.current) return; const { x: endX, y: endY } = endBox.current.getBoundingClientRect(); const { x: startX, y: startY } = startBoxPosition.current; // "LAST" - calculate end position const moveXPosition = endX - startX; const moveYPosition = endY - startY; // "INVERT" - recalculate position based upon current x,y coords setBoxPosition((prevState) => ({ x: prevState.x !== moveXPosition ? moveXPosition : 0, y: prevState.y !== moveYPosition ? moveYPosition : 0 })); }; useLayoutEffect(() => { // "FIRST" - save starting position if (startBox.current) { const { x, y } = startBox.current.getBoundingClientRect(); startBoxPosition.current = { x, y }; } }, []); // "PLAY" - switch between start and end animation via the x,y state and a style property return ( ); } Transition Between Points
{hasMoved ? "End" : "Start"}
P粉1067114252024-04-07 09:16:56
在 sleep
期间,浏览器可能有时间重新计算 CSSOM 框(也称为“执行回流”)。没有它,您的 transform
规则就不会真正应用。
事实上,浏览器会等到真正需要时才应用您所做的更改,并更新整个页面框模型,因为这样做可能会非常昂贵。
当你做类似的事情
element.style.color = "red"; element.style.color = "yellow"; element.style.color = "green";
所有CSSOM将看到的是最新状态,“绿色”
。另外两个就被丢弃了。
因此,在您的代码中,当您不让事件循环实际循环时,也永远不会看到 transStr
值。
但是,依赖 0ms setTimeout
是一个问题调用,没有任何东西可以确保样式在那时重新计算。相反,最好手动强制重新计算。一些 DOM 方法/属性 会同步执行此操作。但请记住,回流可能是一项非常昂贵的操作,因此请务必偶尔使用它,如果代码中有多个位置需要此操作,请务必将它们全部连接起来,以便执行单次回流。 p>
const el = document.querySelector(".elem"); const move = () => { el.style.transition = ""; const transStr = `translate(150px, 0px)`; el.style.transform = transStr; const forceReflow = document.querySelector("input").checked; if (forceReflow) { el.offsetWidth; } el.style.transition = "transform .5s"; el.style.transform = ""; } document.querySelector("button").onclick = move;
.elem { width: 100px; height: 100px; border: 1px solid grey; } .parent { display: flex; padding: 3rem; }