skip to content

In a browser, why does changing an element's `transform: translateX()` normally skip layout and paint work, while changing its `left` does not?

level: middleimportance: must knowfreq 72%

answer

  1. which stage does the property enter
  2. one is layout, one is post-layout
  3. the texture already exists
  4. only the matrix changes
  5. true only once promoted

basics

~20 s

left is a layout property: changing it invalidates geometry, so the browser re-runs layout, repaints the affected content and recomposites. transform on a composited layer changes only the matrix the compositor uses to draw an already-rasterized texture, so layout and paint are skipped entirely.

solid answer

~50 s

They enter the pipeline at different stages. `left` participates in layout — it positions the box inside its containing block — so changing it invalidates layout, and everything downstream of layout must run again: paint the affected content, rasterize it, composite. `transform` is applied after layout; the box keeps the same laid-out geometry and nothing around it moves. If the element already has its own compositing layer, its content is a texture the compositor holds, and a new translation is just a different matrix passed to the GPU — no new paint recording and no re-raster. That is the whole reason `transform` and `opacity` are called compositor-friendly. The caveat is that it only holds when the element is actually promoted; on a non-promoted element the change still forces a repaint of the region it paints into.

code

css · 26 lines
css
.box {
  position: absolute;
  top: 0;
  left: 0;
  width: 120px;
  height: 120px;
  background: #0af;
}

/* invalidates layout on every frame */
.box.expensive {
  animation: move-left 1s linear infinite alternate;
}

/* composite-only on every frame */
.box.cheap {
  animation: move-transform 1s linear infinite alternate;
}

@keyframes move-left {
  to { left: 300px; }
}

@keyframes move-transform {
  to { transform: translateX(300px); }
}

go deeper

for a junior

Know that transform and opacity are the two properties described as compositor-friendly, and that properties like left, top, width and margin force the browser to redo layout.

for a middle

Be ready to derive the cost from the pipeline stage a property enters, and to name what is invalidated in each case: layout onward, paint onward, or the composite parameters only.

for a senior

Demonstrate the caveats — promotion is a precondition, scale can still force re-raster, and a layer whose contents change every frame repaints every frame no matter which property drives the motion.

for a principal

Own the fact that this is a mechanism, not a rule to apply blindly: leaning on transform everywhere trades main-thread work for GPU memory and layer-management overhead, and that trade has a different answer on a low-end phone than on a workstation.

## Two properties, two entry points into the pipeline The pipeline runs style, layout, paint, raster, composite. What makes a property cheap or expensive is simply how early it enters that chain — everything after the entry point must be redone. `left` (together with `top`, `width`, `height`, `margin`, `padding`, `font-size` and friends) is consumed by **layout**. The used value of `left` positions the box relative to its containing block, so changing it means the box's geometry is no longer valid. The browser marks it dirty, re-runs layout for at least that box, then repaints the region, rasterizes the new bitmap, and composites. `transform` is consumed **after layout**. The layout engine has already decided where the box lives and how big it is; a transform is a visual mapping applied to the painted result. Nothing else on the page moves because of it — in-flow siblings keep their positions, and text does not reflow around a translated box. So layout has nothing to redo. ## Why composite-only is nearly free The compositor keeps, per layer, a rasterized texture plus a small set of parameters: a transform matrix, an opacity, a clip. Producing a frame means issuing GPU draw calls with those parameters. Changing the matrix or the opacity changes a handful of numbers; the texture is untouched. There is no display-list re-recording, no rasterization, and — importantly — no main-thread work at all once a composited animation is handed over. ```css /* layout -> paint -> raster -> composite, every frame */ @keyframes slide-expensive { to { left: 300px; } } /* composite only, every frame */ @keyframes slide-cheap { to { transform: translateX(300px); } } ``` Both animations look identical. The first invalidates geometry sixty times a second; the second changes a matrix sixty times a second. ## The caveats that separate a good answer from a slogan **"Composite only" requires a compositing layer.** If the element is not promoted, changing its transform invalidates the paint recording of whatever layer it paints into, and the browser repaints and re-rasterizes that region. Browsers promote automatically when they see a running transform or opacity animation, which is why the rule holds for animations in practice — but a one-off transform write on an ordinary element is a repaint, not a magic trick. **Opacity is not unconditionally free either.** On a promoted layer, opacity is a compositor parameter. On a non-promoted element, changing opacity invalidates paint like any other visual property, and an opacity below 1 also forces the element's content to be composited as a group rather than blended element by element. **Scaling can still cost raster.** Translating a texture is resampling-free in practice, but animating `scale` upward can leave the layer rasterized at too low a resolution — the browser may re-raster at the higher scale to keep it sharp, which is real paint work. Some engines rasterize once at a chosen scale and accept blurriness instead; either way, scale is less uniformly cheap than translate. **The content still has to be painted once.** Promotion does not remove paint cost, it moves it: the layer is recorded and rasterized when it is created, and again whenever its contents change. An animation of `transform` on an element whose text is also being rewritten every frame repaints every frame. **Transform is not layout-neutral for everything.** A transformed element still occupies its original space in flow, but it does become a containing block for fixed and absolutely positioned descendants and it affects hit-testing and overflow. Those are semantics, not cost, but they are why swapping `left` for `transform` is not always a drop-in refactor. ## The mental model to state in an interview Name the stage each property enters and derive the cost from it. `left`, `width`, `margin` enter at layout — the most expensive entry point, because layout of one box can invalidate its subtree and its in-flow siblings. `background-color`, `box-shadow`, `border-radius`, `color` enter at paint — layout is skipped but the recording and its tiles die. `transform` and `opacity` enter at composite, on a promoted layer — nothing is recomputed, only the parameters used to draw an existing texture.

  • Is a transform write on any element composite-only, or does it depend on something?
    It depends on promotion. On an element with its own compositing layer the change is a matrix update. On a non-promoted element the browser must repaint and re-rasterize the region it paints into. Browsers usually promote automatically when a transform or opacity animation is running, which is why the rule holds for animations specifically.
  • Animating `transform: scale()` is sometimes less cheap than animating `translate()`. Why?
    A layer is rasterized at a particular scale. Translating just moves that texture, but scaling up means the texture no longer has enough resolution, so the browser may re-rasterize at the new scale to keep edges and text sharp — real paint work — or keep the old raster and accept a blurry frame.
  • Does an element with a transform still take up its original space in the layout?
    Yes. `transform` is applied after layout, so the box keeps its laid-out position and size, in-flow siblings do not shift, and text does not reflow around it. It does become a containing block for absolutely and fixed-positioned descendants, and it changes hit-testing and overflow.
  • Where does changing `background-color` sit relative to these two?
    In between. It skips layout — geometry is unaffected — but it invalidates the layer's paint recording and its rasterized tiles, so the browser re-records and re-rasterizes that region before compositing. Cheaper than a layout change, materially more expensive than a matrix update.

saying these in an interview costs you the question

  • Claims transform is always free regardless of promotion
  • Says left is slow only because it is an older property
  • Thinks transform changes the element's position in flow
  • Believes GPU-accelerated means no main-thread cost ever exists
  • Assumes opacity below 1 never causes any repaint

context