Transforms

Move, scale and rotate an element without touching layout, and build a real 3D scene.

20of 26
≈ 11Min read
9Parts

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.

the modern way: three separate properties
.card {
  translate: 0 -4px;
  scale: 1.02;
  rotate: -1.5deg;
}
the shorthand still works, and order matters
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

FunctionWhat it does
translate(x, y) / translateX / translateYMove. 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.

the point everything happens around
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.

percentages of itself
.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.

on the parent, not the element
.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 */
}
Which way it turns

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.

a card that flips
.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, rotate and opacity are the four things the compositor can do alone. Everything else costs layout or paint.
  • will-change: transform promotes 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 transform on 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 fixed header 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.

Practice