CSS Transform Functions Table
| Function | What it does | Field note |
|---|---|---|
translate(x, y) | Move from its spot | Percentages refer to the ELEMENT's own size - not the parent |
rotate(deg) | Spin around the origin | transform-origin decides the pivot - default is center |
scale(sx, sy) | Resize visually | Layout is untouched - text stays crisp, unlike zooming a bitmap |
skew(x, y) | Slant the axes | Pure display distortion - rare outside decorative shapes |
matrix() | All six values at once | What the browser reduces transforms to - readable in devtools |
translateZ / 3d | Perspective games | Needs perspective on the parent to show depth |
transform-origin | The pivot point | rotate(45deg) around top-left vs center are different layouts |
chained transforms | translate() rotate() scale() | Order matters - translate-then-rotate differs from rotate-then-translate |
Transforms move the PAINTED result, not the layout: a translated, rotated or scaled element keeps its original box in the flow - neighbors never reflow, text stays vector-crisp, and the browser composites the change on the GPU. That is the entire performance story of modern animation.
Bottom line: animating transform is GPU-cheap while animating top/left is layout thrash - same visual motion, opposite cost. The model has two knobs people miss: transform-origin picks the pivot (rotate around center vs top-left are different layouts), and percentages in translate refer to the ELEMENT's own size, not the parent's.
The honest part: transforms chain, and chains are order-sensitive math - translate(100px) rotate(45deg) lands the rotation in a different place than rotate(45deg) translate(100px). Read the chain left to right as instructions applied to the element's local coordinate system, and the divergence stops being mysterious.
How to use
- Center anything absolutely-positioned: left: 50%; top: 50%; transform: translate(-50%, -50%) - the percentages measuring the element itself is what makes it size-independent.
- Animate only transform and opacity: hover lifts become translate(0, -4px), entrances become translateY + opacity - compositor work, no layout, no jank.
- Set the pivot deliberately: transform-origin: bottom center makes a card lift like a hinge from its base - the default center is one choice, not the law.
Frequently asked questions
Why does transform: translate() beat position tweaks for animation?
Layout versus paint. Changing top/left invalidates layout: the engine recomputes geometry for the element and potentially everything after it, every frame - 60 of those per second is the classic jank. Transform happens after layout, on the composited layer: the element's box never moves, so no reflow is triggered, and the GPU slides the already-painted texture. The exception worth knowing: transform creates a containing block, so fixed-position children re-anchor to the transformed element - the same gotcha as filter.
What does the transform chain order actually change?
Everything about composition. Transforms apply right to left to the element's local coordinates: translate(100px) rotate(45deg) moves the element 100px and then spins it in place, while rotate(45deg) translate(100px) spins first and then moves 100px along the ROTATED axis - so the element lands 100px along a diagonal instead of horizontally. Same functions, different order, different destination. When a chain confuses you, read it as instructions to a person holding the element: 'move right 100px, then turn 45 degrees' is the reverse order of 'turn, then move'.
Why are translate percentages relative to the element itself?
By specification - and it is the property that makes the classic centering trick work. translate(-50%, -50%) shifts the element left and up by HALF ITS OWN size, which exactly cancels the top-left anchor placement at 50%/50% of the parent, for any element size, without JavaScript. Margin-based centering needs known dimensions; translate centering does not. The contrast to remember: percentage values in width or margin resolve against the PARENT; percentage in translate resolves against the element being transformed.
When do I need the 3d functions and perspective?
When depth is the design: card flips (rotateY with a backface), parallax layers, tilt effects. The rule that unlocks them: 3d transforms only look three-dimensional when some ancestor establishes perspective - parent { perspective: 800px } - otherwise rotateY just looks like a horizontal squeeze, because there is no vanishing point. translateZ moves toward that viewer. The cost note: perspective and 3d transforms promote their own compositing layers, so they share the GPU economics of 2d transforms, but preserve-3d and nested 3d contexts get hairy fast - keep the 3d scene shallow.