A transform does not move the box
transform changes how an element is painted, not where its box is. The space it occupied stays reserved, nothing around it shifts, and — because the layout is untouched — the browser can do the whole thing on the compositor.
That is why every piece of motion advice ends with "use transform".
The individual properties
These are independent, so a hover can change translate without redeclaring rotate, and each can have its own transition. That was impossible when everything lived in one transform string.
.card {
translate: 0 -4px;
scale: 1.02;
rotate: -1.5deg;
}
transform: translateY(-4px) scale(1.02) rotate(-1.5deg);
/* these two are NOT the same */
transform: translateX(100px) rotate(45deg); /* move, then spin in place */
transform: rotate(45deg) translateX(100px); /* spin the axes, then move diagonally */
The functions
| Function | What it does |
|---|---|
translate(x, y) / translateX / translateY | Move. Percentages are of the element's own size. |
scale(x, y) | Resize from the origin. Scales the border, the text and the shadow too. |
rotate(30deg) | Spin around the origin. |
skew(10deg, 0) | Slant. Rarely useful, and it distorts text. |
matrix(…) | All of the above at once. Written by tools, not by people. |
translate3d, rotateX/Y/Z, perspective() | The 3D set. |
transform-origin
A door swings on its hinge, not its middle. A menu grows from the button that opened it. Setting the origin is what makes a rotation or a scale look like it has a reason.
transform-origin: 50% 50%; /* the default: the centre */
transform-origin: 0 0; /* top left */
transform-origin: left center;
transform-origin: bottom right;
The centring trick
This is the one case where a percentage refers to the element itself, and it is why the pattern works without knowing the element's width.
.tooltip {
position: absolute;
left: 50%;
translate: -50% 0; /* -50% of the TOOLTIP's width, not the parent's */
}
Perspective makes it three-dimensional
Without perspective, a rotateY is an orthographic squash — the far edge does not get smaller, so it reads as a shrink, not a turn. perspective sets how far the viewer is from the plane: smaller numbers mean a stronger, more dramatic effect.
.scene {
perspective: 1200px; /* the viewer is 1200px away */
perspective-origin: 50% 50%; /* looking at the centre */
}
.scene .leaf {
transform-origin: left center;
transition: rotate 600ms cubic-bezier(.4, 0, .2, 1);
}
.scene:hover .leaf {
rotate: y 70deg; /* swings away like a door */
}
A positive rotateY pushes the right edge away from the viewer. With transform-origin: left center that reads as a door opening inwards — the reveal starts at the free edge and sweeps across. A negative angle brings the edge towards you instead, which fills the frame and hides the very thing it was meant to reveal.
preserve-3d and backface-visibility
transform-style: preserve-3d is what lets a child sit at a real angle to its parent instead of being flattened into it. Note that a filter, opacity below 1, overflow or clip-path on the same element forces flattening — a common reason a 3D scene suddenly goes flat.
.flip {
perspective: 1000px;
}
.flip .inner {
transform-style: preserve-3d; /* children keep their own 3D positions */
transition: rotate 600ms;
}
.flip:hover .inner { rotate: y 180deg; }
.flip .front,
.flip .back {
position: absolute;
inset: 0;
backface-visibility: hidden; /* do not show the mirror image */
}
.flip .back { rotate: y 180deg; }
Performance
translate,scale,rotateandopacityare the four things the compositor can do alone. Everything else costs layout or paint.will-change: transformpromotes an element to its own layer ahead of time. Use it on the few elements that are about to animate, remove it afterwards, and never put it on everything — each layer costs memory.- Animating
transformon a very large element still costs: the layer has to be rasterised at that size. - A transform creates a containing block for fixed and absolute descendants, and a stacking context. That is why a
fixedheader inside a transformed ancestor scrolls with it.
Text and transforms
Scaling text re-rasterises it, and a scale of 1.02 on a heading can look soft for the duration of the animation. Scale containers and images; move text with translate instead.