AniUI Academy

Transforms

translate(), scale(), rotate() and skew(), combining multiple functions in one declaration, transform-origin, and why transform/opacity are best for smooth animation.

7 min read

transform moves, resizes, rotates or skews an element without changing the normal document flow around it — the element visually shifts, but the space it was originally occupying doesn't reflow to match. That distinction turns out to matter for performance, which this lesson ends on.

The four 2D transform functions

.moved   { transform: translateX(20px); }
.grown   { transform: scale(1.2); }
.spun    { transform: rotate(15deg); }
.slanted { transform: skewX(10deg); }
  • translate() — shifts the element. translateX() and translateY() move along a single axis; translate(x, y) does both at once. Positive x moves right, positive y moves down.
  • scale() — resizes the element. scale(1.2) grows it to 120% in both dimensions; scale(1, 0.5) scales width and height independently. scaleX()/scaleY() do one axis alone. Values below 1 shrink the element.
  • rotate() — spins the element around a fixed point, by angle (deg). Positive values rotate clockwise.
  • skew() — slants the element along an axis, distorting its shape rather than rotating it rigidly. skewX()/skewY() skew one axis; skew(x, y) does both.

None of these change the element's width, height, or position in the normal document flow as far as its neighbors are concerned — a transformed element still occupies its original space in the layout, it just visually renders shifted, scaled or rotated on top of that space. Neighboring elements don't reflow around a transformed element the way they would if you'd actually changed its width or margin.

Combining multiple functions

Multiple transform functions can be listed in a single declaration, separated by spaces, and they apply in the order written:

.card {
  transform: translateX(10px) rotate(5deg);
}

Order matters. translateX(10px) rotate(5deg) moves the element first, then rotates it around its own (now-shifted) center. rotate(5deg) translateX(10px) rotates first, which changes what direction "X" even points in for the subsequent translate, since the element's local coordinate system rotated with it — producing a visibly different result from the first example. If a combined transform looks wrong, try reversing the order of the functions before anything else.

transform-origin

By default, rotate() and scale() both pivot around the element's own center. transform-origin moves that pivot point:

.corner-rotate {
  transform-origin: top left;
  transform: rotate(15deg);
}

With the default center origin, rotating an element spins it in place, its center staying put. With transform-origin: top left, that same rotation instead pivots around the element's top-left corner, swinging the rest of the element around that fixed point — the difference between a clock hand sweeping around its center pin and a door swinging on a hinge at one edge. The same idea applies to scale(): an origin at top left grows the element outward from that corner rather than outward from the middle in every direction.

transform-origin accepts keywords (center, top, bottom left, etc.) or explicit lengths/percentages measured from the element's own top-left corner.

Why transform and opacity are the properties to animate

This is a genuine, well-established performance distinction, not a stylistic preference. Changing most CSS properties forces the browser to redo work further up its rendering pipeline:

  • Changing width, height, top, left, or margin can trigger layout (recalculating the position and size of the changed element and potentially everything around it) followed by paint (redrawing actual pixels) — the most expensive path.
  • Changing something like background-color typically skips layout but still triggers paint.
  • Changing transform or opacity typically requires neither — the browser's compositor (a separate stage that combines already-painted layers) can usually apply the visual change directly, without recalculating layout or repainting pixels.

Because the compositor step is comparatively cheap and can often run smoothly at 60 frames per second even on modest hardware, animating transform and opacity tends to stay smooth, while animating width or top on the same element is far more likely to visibly stutter — especially on a page with a lot of other content that would also need to reflow around the change.

In practice, this means: to slide an element in, animate transform: translateX(...) rather than animating left or margin-left. To fade something in, animate opacity rather than toggling display or visibility outright (which, as the previous lesson covered, aren't transition-able anyway). Both approaches often produce the same visual result, but one is built to stay smooth under animation and the other isn't.

Try it yourself

Try it yourself
Loading playground...

What to remember

  • translate(), scale(), rotate() and skew() move, resize, spin and slant an element without affecting the layout space around it.
  • Multiple functions in one transform apply in the order written — reversing the order can produce a visibly different result.
  • transform-origin moves the pivot point that rotate()/scale() work from; the default is the element's own center.
  • Prefer animating transform and opacity over width, top, or margin — the former can usually be handled by the compositor alone, without triggering layout or paint, which is what keeps an animation smooth.