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>