Home > Mobile >  Why does omitting a 0ms sleep break my css transition?
Why does omitting a 0ms sleep break my css transition?

Time:12-29

I was trying to implement the Edit React Transition Between Points


import { useState, useRef, useLayoutEffect } from "react";
import "./styles.css";

type BoxXYPosition = { x: number; y: number };

export default function App() {
  const startBox = useRef<HTMLDivElement | null>(null);
  const startBoxPosition = useRef<BoxXYPosition>({ x: 0, y: 0 });

  const endBox = useRef<HTMLDivElement | null>(null);

  const [boxPosition, setBoxPosition] = useState<BoxXYPosition>({
    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 (
    <main className="app">
      <h1>Transition Between Points</h1>
      <div className="container">
        <div
          ref={startBox}
          className="box start-point"
          style={{
            transform: hasMoved
              ? `translate(${x}px, ${y}px) rotateZ(360deg)`
              : ""
          }}
        >
          {hasMoved ? "End" : "Start"}
        </div>
        <div className="end-container">
          <div ref={endBox} className="box end-point" />
        </div>
      </div>
      <button
        type="button"
        onClick={updatePosition}
      >
        Move to {hasMoved ? "Start" : "End"}
      </button>
    </main>
  );
}

CodePudding user response:

What happens is that the browser may have time to recalculate the CSSOM boxes (a.k.a "perform a reflow"), during that sleep. Without it, your transform rule isn't ever really applied.
Indeed, browsers will wait until it's really needed before applying the changes you made, and update the whole page box model, because doing so can be very expensive.
When you do something like

element.style.color = "red";
element.style.color = "yellow";
element.style.color = "green";

all the CSSOM will see is the latest state, "green". The other two are just discarded.

So in your code, when you don't let the event loop actually loop, the transStr value is never seen either.

However, relying on a 0ms setTimeout is a call to issues, there is nothing that does ensure that the styles will get recalculated at that time. Instead, it's better to force a recalc manually. Some DOM methods/properties will do so synchronously. But remember that a reflow can be a very expensive operation, so be sure to use it sporadically, and if you have multiple places in your code in need of this, be sure to concatenate them all so that a single reflow is performed.

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;
}
<label><input type=checkbox checked>force reflow</label>
<button>move</button>
<div class=parent>
  <div class=elem></div>
</div>

Or with OP's code.

  • Related