skip to content

In CSS, why are `transform` and `opacity` recommended as animation targets instead of `left`, `top`, or `width`?

level: middleimportance: must knowfreq 72%

answer

  1. three buckets: geometry, pixels, neither
  2. layout box never changes
  3. siblings do not move
  4. transform applies after layout
  5. cheaper, not free

basics

~20 s

Animating transform and opacity changes neither an element's layout box nor the pixels painted inside it, so engines can run those animations on the compositor. Animating left, top, or width re-runs layout on every frame.

solid answer

~50 s

Every property change lands in one of three buckets: it changes geometry, it changes painted pixels, or it changes neither. `left`, `top`, `width`, `margin` and `font-size` change geometry, so the browser has to resolve layout again on every frame — and not just for that element, since siblings and descendants can move too. `background-color` or `box-shadow` keep the geometry but change the pixels, so the element repaints each frame. `transform` and `opacity` do neither: a transform is a visual displacement applied after layout, so the element keeps exactly the box it had and nothing around it notices, and `opacity` only changes how the already-painted surface is blended. That means an engine can animate them by re-drawing an existing surface with a new matrix or alpha. It is cheaper, not free — a huge surface still costs memory and rasterisation.

go deeper

for a junior

Know the rule of thumb and say it cleanly: prefer animating transform and opacity, and reach for translateX instead of left when sliding something.

for a middle

Be ready to explain the mechanism — that transform is applied after layout so the box and its neighbours never move, and that layout properties force sizing to be resolved again on every frame.

for a senior

Show judgment: name the cases where transform is not enough (large surfaces, blurry upscaling, animations that genuinely must reflow siblings) and say you would measure rather than assume the rewrite helped.

for a principal

Own the tradeoff at the system level — when a motion vocabulary should be constrained to transform and opacity by convention, what that costs designers, and where you accept a layout animation because the interaction demands it.

## The three buckets Before any pixels exist, the browser has to answer three separate questions about a box: how big is it and where does it sit (geometry), what colour is every pixel inside it (painting), and how are the finished surfaces stacked and drawn onto the screen (compositing). An animation re-asks whichever of those questions your animated property touches — on every single frame, which on a typical display means sixty times a second. So the useful mental model is not "which properties are fast" but "which stage does this property drag back into the frame loop". - **Geometry properties** — `width`, `height`, `top`, `left`, `right`, `bottom`, `margin`, `padding`, `border-width`, `font-size`, `flex-basis`. Changing one changes the size or position of the box in flow, so the sizing algorithm has to run again. The cost is not local: a wider box can rewrap text, push siblings, and resize an auto-sized ancestor. - **Paint properties** — `background-color`, `color`, `box-shadow`, `border-radius`, `outline`, `background-position`. Geometry is untouched, but the element's pixels differ, so its content has to be drawn again into a surface. - **`transform` and `opacity`** — neither. This is the whole point of the leaf. ## Why `transform` is different from `left` `transform` is not a layout property at all. The box is laid out first, at its normal size and position, and the transform is then applied as a visual mapping of that finished box. The element still *occupies* its untransformed space: elements after it in flow do not move, an auto-height parent does not shrink, and nothing rewraps. That is a correctness property as much as a performance one — if you actually want the surrounding layout to react, `transform` is the wrong tool and `left`/`margin` is right. `opacity` is similar in spirit: it does not change what is drawn inside the element, only the alpha applied to the whole result when it is composited. Because neither one invalidates layout or the element's own painted content, an engine can keep the surface it already produced and simply re-draw it with a different matrix or alpha per frame. Browsers do this automatically for elements running a `transform` or `opacity` animation — you do not have to ask for it. ```css /* re-runs layout every frame */ .panel { position: absolute; left: 0; transition: left 300ms ease; } .panel.is-open { left: 320px; } /* no layout, no repaint of the panel's contents */ .panel { transform: translateX(0); transition: transform 300ms ease; } .panel.is-open { transform: translateX(320px); } ``` ## Translating the pattern Most "expensive" animations have a transform-shaped equivalent: - moving something: `left`/`top` → `translateX()`/`translateY()` - growing something: `width`/`height` → `scale()` (accepting that the content scales too, including text and borders) - revealing something: animating `height` from `0` → animating `transform: scaleY()` or a clip, or `opacity` where a fade is acceptable - `display: none` → `display` is not smoothly animatable on its own; a fade uses `opacity` plus `visibility` ## The caveats that separate a real answer from a slogan **Cheap is not free.** The surface still has to exist. A full-screen element being translated costs memory proportional to its pixel area, and a great many simultaneously animated elements will cost regardless of which property you picked. **Scaling can look soft.** A surface rastered at one size and then scaled up can appear blurry until it is re-rastered, which is why scaling large text or crisp borders sometimes looks worse than animating the real size would. **The rest of the frame still counts.** If the same interaction also inserts DOM, toggles a class that changes `width` somewhere else, or animates a `filter` blur, you have paid the layout or paint cost anyway. `transform` only exempts the transform. **Some properties are worse than layout.** Animating `box-shadow` blur or `filter: blur()` re-runs an expensive raster operation per frame even though geometry never changes. The honest framing in an interview: `transform` and `opacity` are the two properties whose animation *can* skip layout and repaint entirely, which is why they are the default choice — and if the design genuinely requires animating a layout property, you say so and measure rather than contorting the markup.

  • If `transform` never affects layout, how do you animate a panel that genuinely needs to push its siblings aside?
    Then you need a layout property — animating `width`, `margin`, or a grid track is the honest tool, because pushing siblings *is* a layout change. Keep the animated element small, animate a single dimension rather than a whole subtree, and consider whether the siblings can instead be overlaid so a `transform` will do.
  • Does animating `opacity` from 1 to 0 remove the element from interaction the way `display: none` would?
    No. A fully transparent element is still laid out, still hit-tested, and still reachable by assistive tech and keyboard focus. Pair the fade with `visibility: hidden` (or `display: none` after the transition) so the invisible element stops swallowing clicks and stops appearing in the tab order.
  • Why is animating `box-shadow` still expensive even though it changes no geometry?
    Because it changes painted pixels. A shadow's blur is a per-frame raster operation over an area larger than the element itself, so each frame repaints. The usual workaround is to stack a pre-rendered shadow layer as a pseudo-element and animate its `opacity` instead, which keeps the shadow drawn once.

Animating left is redrawing the page; animating transform is sliding an already-printed sticker across it.

saying these in an interview costs you the question

  • Says a translated element pushes its siblings along with it
  • Claims transform animations are always free regardless of element size
  • Thinks translateZ(0) must be added to every animation
  • Believes animating width is fine because it is only one property
  • Says opacity animation repaints the element's contents each frame

context