Motion Transform Shorthands Are Not GPU-Accelerated
MUST: In Motion/Framer Motion, animate the full `transform` string, not the `x`/`y`/`scale` shorthands: the shorthands are interpolated per `requestAnimationFrame` on the MAIN THREAD and drop frames under load, while `transform` as a whole string (along with `opacity`, `filter`, `clipPath`) is handed to the Web Animations API and ticked by the compositor. This is the false floor under `animations-compositor-friendly` — `animate={{ x: 100 }}` looks compliant and is not.
Animate the full transform string in Motion, because x, y, and scale are ticked on the main thread
// main-thread rAF: animate={{ x: 100 }}
// compositor/WAAPI: animate={{ transform: "translateX(100px)" }}Bad
Good
Why it matters
This is the false floor under every "animate transform and opacity" rule you have already read. Writing animate={{ x: 100 }} looks like you obeyed them — it does compile down to a transform — but the value is interpolated by the library on each requestAnimationFrame tick and written to style, so it lives and dies on the main thread and drops frames the moment anything else is busy.
Motion only hands an animation to the Web Animations API when the animated property is on its accelerated list — opacity, filter, clipPath, and transform as a whole string — and the x / y / scale shorthands are not on it. Animate transform: "translateX(100px)" instead and the compositor ticks it, so it keeps moving even while the main thread is blocked. Distinct from setting transform on the element rather than a parent CSS variable: that rule is about a style-recalc storm across descendants, this one is about which thread owns the tween.