Suggest an editImprove this articleRefine the answer for “Why transform is better for animations than top, left”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**CSS `transform`** skips layout and paint on every animation frame; `top`/`left` reruns both from scratch. ```css /* Slow: triggers full layout recalculation every frame */ @keyframes bad { to { top: 50px; } } /* Fast: GPU composite only, no layout involved */ @keyframes good { to { transform: translateY(50px); } } ``` **Key point:** `transform` promotes the element to a GPU layer; `top`/`left` forces CPU layout recalculation on every frame.Shown above the full answer for quick recall.Answer (EN)Image**CSS `transform`** moves an element by applying a matrix to an already-painted layer, so the browser skips layout and paint on every animation frame. `top` and `left` change where an element sits in the document, which forces a full recalculation every frame. ## Theory ### TL;DR - `top`/`left` is like rearranging furniture: everything around it shifts. `transform` is like sliding a projector image: the room does not move. - The rendering pipeline has three steps: layout, paint, composite. `transform` only touches the last one. `top`/`left` restarts from the first. - Decision rule: any visual motion (hover, scroll trigger, slide-in) should use `transform`. Use `top`/`left` only for static positioning that never animates. - GPU handles `transform` asynchronously off the main thread. `top`/`left` runs on the CPU within every 16ms frame budget. ### Quick example ```css /* Bad: browser recalculates layout every frame */ @keyframes bad { to { top: 50px; left: 50px; } /* layout + paint on each frame */ } /* Good: browser skips layout and paint entirely */ @keyframes good { to { transform: translate(50px, 50px); } /* composite only */ } .box-bad { position: relative; animation: bad 1s infinite; } .box-good { animation: good 1s infinite; } ``` On a complex page, `bad` drops to 10-20fps during scroll. `good` holds 60fps because the GPU runs it independently of the main thread. ### Key difference Browsers render in three steps: **layout** (calculate positions), **paint** (draw pixels), **composite** (assemble layers). Animating `transform` promotes the element to its own GPU layer and only updates the composite step each frame. `top` and `left` invalidate the element's geometry, which forces the browser to redo layout from the document root outward, then repaint all affected areas. One frame costs a few milliseconds. At 60fps the total budget per frame is 16ms. ### When to use - Visual movement (hover lift, slide-in, parallax): `transform: translate` - Scaling without shifting siblings: `transform: scale` - Rotation or flip animations: `transform: rotate` - Static positioning, precise flow control: `top`/`left` is fine - Mobile or 60fps required: always `transform` ### Comparison table | Aspect | `transform` | `top` / `left` | |---|---|---| | Rendering steps | Composite only (GPU) | Layout + Paint + Composite | | Typical fps on complex pages | 60fps | 10-20fps under load | | Affects document flow | No | Yes, shifts siblings | | GPU acceleration | Automatic layer promotion | None (CPU-bound) | | Scroll performance | Unaffected | Can trigger reflow on scroll | | When to use | All animations, transitions | Static positioning only | ### How the browser handles this When you animate `transform`, Chrome's rendering engine (Blink) promotes the element to a `cc::Layer` and hands it to the GPU backend (Skia). Subsequent frames only update the composite matrix. No layout tree involved. `top`/`left` invalidates `RenderBox` geometry: the browser walks up the layout tree, marks dirty nodes, reruns layout, then queues paint for all affected regions. On a page with a sidebar, header, and several floating cards, that is a lot of nodes touched per frame. `will-change: transform` hints to the browser to promote the element early, avoiding a one-frame delay when animation starts. Use it only on elements that are actually about to animate. Applying `will-change: transform` to 100+ list items simultaneously can consume 200MB of GPU memory and crash the tab. ### Common mistakes **Animating `width` or `height` to simulate a grow effect:** ```css /* Triggers layout recalculation every frame */ @keyframes grow-bad { to { width: 200px; } } /* Correct: scale is composite-only */ @keyframes grow-good { to { transform: scale(2); } } ``` **Using `position: relative` + `top` for a slide-in:** ```css /* Shifts all following elements while the animation runs */ @keyframes slide-bad { from { top: -20px; } to { top: 0; } } /* Does not touch document flow at all */ @keyframes slide-good { from { transform: translateY(-20px); } to { transform: translateY(0); } } ``` **Overusing `will-change` on static lists:** ```css /* Leaks GPU memory on 50+ items */ .list-item { will-change: transform; } /* Better: promote only on hover */ .list-item:hover { will-change: transform; } ``` **Mixing `transform` with `margin` for offset:** ```css /* margin: 0 auto centers in flow; the translate offset fights it */ .bad { margin: 0 auto; transform: translateX(10px); } /* Correct: chain transforms for combined offset */ .good { left: 50%; transform: translateX(-50%) translateX(10px); } ``` ### Real-world usage - React Transition Group uses `transform: translate3d` for page slide transitions - Framer Motion defaults all `motion.div` movement to `transform` values - GSAP compiles `x: 100` to `translateX(100px)` internally - Swiper.js runs carousel swipes via `translate3d` for touch performance - Tailwind's `animate-pulse` uses `scale` and `opacity`, not `width`/`height` ### Follow-up questions **Q:** Why does `transform` not affect layout even though the element visually moves? **A:** `transform` applies a matrix to the painted layer after layout is complete. The document flow sees the original untransformed box. Sibling elements have no knowledge of the visual position change. **Q:** When does the browser actually promote an element to a composite layer? **A:** On animated `transform`, `opacity`, or 3D properties, and when `will-change: transform` is set. The Layers panel in Chrome DevTools shows current layer state. **Q:** Does `transform` give the same pixel precision as `top`/`left`? **A:** Yes. Fractional values like `translateX(0.5px)` render through GPU subpixel blending, which is actually smoother. If you see blurry edges on fractional values, add `backface-visibility: hidden` to force a clean layer. **Q:** How do you debug animation jank in production? **A:** Open DevTools Performance tab and record during the animation. Look for "Recalculate Style" and "Layout" entries in the flame chart. Those spikes mean layout thrash. A clean `transform` animation shows only "Composite Layers." **Q:** Any Safari or iOS differences? **A:** On iOS 12 and earlier, `transform: translateZ(0)` was needed to force layer promotion. Since iOS 13, standard `transform` works without the hack. Still worth testing on real devices, not just simulators. ## Examples ### Hover card lift A common pattern in product UIs: a card lifts on hover. The wrong way is `top: -8px` on hover. That shifts the card in the document flow, makes the page jump, and triggers layout. The right way: ```css .card { transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .card:hover { transform: translateY(-8px) scale(1.02); } ``` No siblings shift. No layout recalculation. The GPU handles the entire transition, and the effect works just as well inside a grid of 100 cards as it does with one. ### Slide-in notification ```jsx // React component: notification slides in from the right function Notification({ visible, message }) { return ( <div style={{ transform: visible ? 'translateX(0)' : 'translateX(110%)', transition: 'transform 0.3s ease-out', position: 'fixed', right: 16, bottom: 16, }} > {message} </div> ); } ``` `position: fixed` handles where the element sits in the viewport. `transform` handles the animation. Those are two separate concerns, and keeping them separate is what makes this pattern work well. Animating `right` instead of `transform` is something I have seen go wrong in production code. It looks fine locally, then drops frames on a budget Android. For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.