Topic 10.2
Transforms
In one line
The transform property moves, rotates, scales and skews an element's painted box, around a point set by transform-origin, without changing the layout around it; with perspective the same functions work in 3D.
Think of it like this
A photo stuck on a fridge with one magnet. You can slide it, spin it around the magnet, or swap it for a bigger print, and the magnet stays exactly where it was. The other photos on the fridge don't move to make room either. A transform does the same to an element: the page keeps the element's original space (the magnet's spot), and only the way the box is drawn changes.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Transform
- A change to how an element is drawn (moved, turned, resized or slanted) that doesn't change the layout around it.
- Transform function
- One step in a transform, like
translate(),rotate(),scale()orskew(). - Transform origin
- The fixed point a transform happens around. The centre of the box by default.
- deg and turn
- Units for angles:
360degor1turnis one full circle. - Bounding rectangle
- The smallest upright rectangle that covers where the element really appears on screen.
- Containing block
- The box that a positioned element measures its position from.
- Z axis
- The direction pointing out of the screen towards you. 3D transforms can move and turn along it.
- Perspective
- How far your eye is from the screen in a 3D scene. Smaller distances make 3D look stronger.
- Backface
- The back side of an element, which you see when it is turned more than 90 degrees away from you.
Step by step
01Move a box without moving the page
Give the middle box transform: translateX(40px). It slides right, but the boxes before and after it stay exactly where they were, and the gap it left is still there. The layout was done first; the transform only changes the drawing.
.middle {
transform: translateX(40px);
}02Rotate and scale around the origin
rotate(15deg) turns the box clockwise around its centre. scale(1.2) makes it 20% bigger in every direction from its centre. Combine them in one list; a second transform declaration would replace the first, not add to it.
.tile {
transform: rotate(15deg) scale(1.2);
}
/* Wrong: the second rule replaces the first. */
.tile:hover {
transform: scale(1.4); /* rotation is lost */
}03Change the pivot point
With transform-origin: left top, the same rotation swings the box like a door hinged at its top-left corner. Try bottom center for a sign that wobbles on its base.
.door {
transform-origin: left top;
transform: rotate(20deg);
}04Use individual properties for independent changes
With translate, rotate and scale as separate properties, a hover can change the scale while an animation changes the rotation, and neither overwrites the other.
.icon {
rotate: 10deg;
transition: scale 0.2s;
}
.icon:hover {
scale: 1.3; /* the 10deg rotation stays */
}05Build a 3D flip card
The parent gets perspective. The card gets transform-style: preserve-3d so its two faces share 3D space. The back face starts turned 180deg, and both faces hide their backs. Turning the card 180deg on hover shows the back.
.scene { perspective: 600px; }
.card {
position: relative;
transform-style: preserve-3d;
transition: transform 0.6s;
}
.scene:hover .card { transform: rotateY(180deg); }
.face { position: absolute; inset: 0; backface-visibility: hidden; }
.back { transform: rotateY(180deg); }Try it yourself
- 1
Predict the matrix
In the first example, change the transform to
translate(20px, 10px) scale(2). Predict the bounding width and the matrix before running (120andmatrix(2, 0, 0, 2, 20, 10)). Does C move? (No.) - 2
Swap the order
In the second example, change box 2 to
rotate(-90deg) translate(100px). Predict where it goes (up, off the top: y becomes -80). Then tryrotate(180deg) translate(100px). - 3
Make the 3D stronger
In the flip card, change
perspective: 600pxto200px, then to2000px. Predict which one looks more dramatic (the smaller distance: your eye is closer). Then flip vertically by usingrotateX(180deg)on both the hover rule and.back.
Code & diagrams
B is drawn 90px wide and overlaps its neighbours, but its layout box is still 60px and C hasn't moved. `getComputedStyle` reports every transform as one `matrix()`: a, b, c, d hold the scale and rotation, the last two numbers the translation.
Preview
Console
Nothing logged yet.Expected console output
layout size (offsetWidth): 60
drawn size (bounding rect): 90
C still starts at x = 170
transform: matrix(1.5, 0, 0, 1.5, 20, 10)Both boxes start at (20, 20). Box 1 moves right, then spins in place. Box 2 spins first, so its own x-axis points down and the same `translate(100px)` moves it down.
Preview
Console
Nothing logged yet.Expected console output
box 1 x: 120 y: 20
box 2 x: 20 y: 120Hover the area: all three turn 30deg, each around its own origin. Hover one tile: `scale` adds to the rotation instead of replacing it. The origin is reported in px (`top left` is `0px 0px`). The individual `rotate` property is separate from `transform`, which stays `none`.
Preview
Console
Nothing logged yet.Expected console output
origin: 0px 0px
rotate before hover: none
transform: noneRemove `perspective` and the flip looks flat (the card only gets narrower). Remove `backface-visibility: hidden` and you see the front's mirrored text through the back.
Preview
Console
Nothing logged yet.Even `translateX(0)`, which moves nothing, makes the parent the containing block, so `top: 0; left: 0` means the parent's corner (50px down, 20px in). With `transform: none` the element is fixed to the viewport again. `filter`, `perspective`, `will-change: transform` and `contain: paint` do the same.
Preview
Console
Nothing logged yet.Expected console output
with transform: top 50 left 20
without transform: top 0 left 0Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Two transform declarations
Write .btn { transform: translateY(-2px); } and .btn:active { transform: scale(0.95); }, expecting the pressed button to stay raised and shrink.
Break #2
Transform a plain span
Write span.badge { transform: rotate(-5deg); } on a <span> inside a sentence.
Break #3
A modal that scrolls away
Put a position: fixed modal inside a wrapper that has transform: translateZ(0) (an old "performance hack") and scroll the page.
Myth vs fact
Myth
transform: scale(2) makes the element take up twice the space.
Fact
Layout is unchanged; only the drawing is bigger. Neighbours don't move, and the element can overlap them. Change width if you want the layout to change.
Myth
translateZ(0) is a free performance boost.
Fact
It forces a separate compositor layer, which costs memory and can blur text, and it changes stacking and containing blocks. Use will-change deliberately and only where measuring shows a benefit (Topic 10.5).
Myth
Transform functions can be written in any order.
Fact
Each function works in the coordinate system left by the previous one, so rotate() then translate() and the reverse give different results.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Every 2D transform list is reduced to a single 3×3 matrix
matrix(a, b, c, d, e, f), and 3D ones to a 4×4matrix3d(). When animating between two lists with matching functions, the browser interpolates each function; when they don't match, it decomposes both matrices (into translate, rotate, scale, skew) and interpolates those, which can make rotations take an unexpected path. Keep function lists in the same order for predictable animation. - ▸
getBoundingClientRect()accounts for transforms;offsetLeft,offsetWidthandclientWidthdon't. Hit-testing (which element a click lands on) uses the transformed shape, so a scaled-up button is clickable over its whole drawn area. - ▸
The
perspectiveproperty on a parent gives all children one shared vanishing point; theperspective()function inside a child's owntransformgives each element its own, so a row of tiles looks different.perspective()must come first in the list to affect the functions after it. - ▸
transform-box(view-box,fill-box,border-box) decides whattransform-originpercentages refer to. It matters mostly for SVG, where the default origin is the top-left of the SVG canvas, not the shape;transform-box: fill-box; transform-origin: centermakes an SVG shape rotate around its own centre.
Remember this
- 1
transformtakes a list of transform functions.translate(x, y)moves the box (translateX()andtranslateY()move along one axis).rotate(angle)turns it; angles are written45deg,0.25turn(a quarter turn) or in radians1.57rad, and positive angles turn clockwise.scale(x, y)stretches it (scale(2)doubles both ways,scale(1, 0.5)squashes it to half height, and a negative value likescale(-1, 1)mirrors it).skew(x, y)slants it like a parallelogram. Percentages intranslate()are relative to the element's own size, sotranslate(-50%, -50%)moves a box back by half its width and height (the classic centering trick from Topic 6.5). - 2
Transforms don't affect layout. The browser first lays out the page as if there were no transform, then draws the element moved, turned or scaled. Neighbours don't shift, the parent doesn't grow, and
offsetWidthstill reports the untransformed size.getBoundingClientRect()is different: it returns the rectangle the transformed box actually covers on screen. This is why transforms are cheap to animate (no layout) and why a scaled-up card can overlap its neighbours. - 3
Every transform happens around the transform origin, a fixed point set by
transform-origin. The default is the centre,50% 50%, sorotate()spins around the middle andscale()grows outward from the middle.transform-origin: top left(or0 0) makes a box rotate like a door around its top-left corner and grow towards the bottom right. The origin doesn't move the box by itself; it only changes the pivot point. - 4
Order matters.
transform: translate(100px) rotate(90deg)moves right 100px, then turns in place.transform: rotate(90deg) translate(100px)turns first, so the box's own "right" now points down, and the translate moves it down 100px. Read the list from left to right as instructions to the box's own coordinate system. Modern CSS also has individual transform properties,translate,rotateandscale(Baseline 2022): they are always applied in that fixed order (translate, then rotate, then scale), then thetransformlist, and each can be changed or animated on its own without repeating the others. - 5
A transform (any value except
none) has side effects you must know: the element becomes a stacking context (itsz-indexchildren are layered inside it, Topic 6.3), and it becomes the containing block for descendants withposition: absoluteandposition: fixed. So aposition: fixedmodal inside a transformed parent is positioned against the parent, not the viewport, and scrolls with it. Transforms also don't apply to plain inline boxes like a<span>in text; give itdisplay: inline-blockfirst. - 6
3D transforms add a third axis, Z, pointing out of the screen towards you.
rotateX()tips the box forward or back (around the horizontal axis),rotateY()turns it like a door (around the vertical axis),rotateZ()is the same asrotate(), andtranslateZ()moves it closer or further. To look 3D, the scene needs perspective:perspective: 600pxon the parent sets how far the viewer's eye is from the screen (smaller is more dramatic), andperspective-originsets where the eye looks from.transform-style: preserve-3don a parent keeps its children in one shared 3D space (needed for a flip card with two faces), andbackface-visibility: hiddenhides a face when it's turned away from you.
Explain it without notes
What does transform do to an element, and how is it different from changing left or width?
What is transform-origin, and what is its default?
Why does transform: rotate(90deg) translate(100px) move the element differently from translate(100px) rotate(90deg)?
Which side effects does a transform have on stacking and positioning?
What do perspective, transform-style: preserve-3d and backface-visibility each do in a 3D flip card?
Practice
Make a .card that lifts 6px and grows 3% on hover, smoothly, without moving any other card.
Center a 120px × 80px .popup inside a position: relative parent using position: absolute and a transform.
Make a .needle (a thin bar) that rotates 45deg around its bottom end when its .gauge parent is hovered.
Trade-offs
- ↔
Transforms animate cheaply because they skip layout, but for the same reason the page doesn't make room: use them for motion and emphasis, not to change the real size of things.
- ↔
Individual
translate/rotate/scaleproperties compose neatly, but their order is fixed (translate, rotate, scale); when you need another order, use thetransformlist. - ↔
3D transforms look impressive but can cause blurry text while animating and extra GPU memory for layers; keep them for small, purposeful effects.
Done when you can
I can use
translate,rotate,scaleandskew, with the right units.I know transforms don't change layout, and I can see the difference between
offsetWidthandgetBoundingClientRect().I can change the pivot with
transform-origin.I can predict how the order of transform functions changes the result.
I know a transform creates a stacking context and traps
position: fixed.I can build a 3D flip card with
perspective,preserve-3dandbackface-visibility.