为什么省略 0ms sleep 会破坏我的 css 转换?
P粉659516906
P粉659516906 2024-04-06 16:29:50
0
2
687

我正在尝试实现 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粉659516906
P粉659516906

全部回复(2)
P粉022285768

您正在使用普通的 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粉106711425

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


热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板