搜尋

首頁  >  問答  >  主體

為什麼省略 0ms sleep 會破壞我的 css 轉換?

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

我懷疑這是一些我不理解的事件循環魔法。有人可以幫我解釋一下嗎?

P粉659516906P粉659516906231 天前673

全部回覆(2)我來回復

  • P粉022285768

    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"}
    ); }

    回覆
    0
  • P粉106711425

    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 方法/屬性 會同步執行此操作。但請記住,回流可能是一項非常昂貴的操作,因此請務必偶爾使用它,如果程式碼中有多個位置需要此操作,請務必將它們全部連接起來,以便執行單次回流。

    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;
    }
    
    
    

    回覆
    0
  • 取消回覆