Module 7: Visual Effects, Transitions & Animations
Transforms: translate, rotate, scale and skew
How the transform property moves pixels without moving layout, why order matters, how the origin works, and the side effects of using a transform.
A transform changes how an element is drawn without changing how it is laid out. The element still occupies its original space and still pushes its neighbors the same way. Only the painted result is moved, rotated, scaled or skewed. That is exactly why transforms are cheap and why they behave differently from margin or top.
The functions#
.a { transform: translate(40px, 20px); } /* move right 40, down 20 */
.b { transform: translateX(-50%); } /* percent of the element's OWN width */
.c { transform: rotate(15deg); } /* clockwise */
.d { transform: scale(1.1); } /* 110% in both directions */
.e { transform: scale(1, 0.5); } /* x, y separately */
.f { transform: skew(10deg, 0); } /* slant */
Percentages in translate refer to the element's own size, not its parent's. That detail makes centering possible:
.centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
top: 50% puts the element's top-left corner at the center of the parent. The transform then moves it back by half its own width and height, so the element's center lands on the parent's center.
Layout does not move#
Before transform After translateX(100px)
┌────┐┌────┐┌────┐ ┌────┐ ┌────┐┌────┐
│ A ││ B ││ C │ │ A │ ┌────┐│ C │
└────┘└────┘└────┘ └────┘ │ B │└────┘
└────┘
B's old slot stays reserved; C does not move to fill it
The transformed element's original space is preserved. Neighbors do not react. A scaled-up element can overlap its neighbors and does not push them. This is usually what you want for hover effects, and unexpected when you try to use a transform for layout.
Scrollable overflow is affected, though. A transform can extend the scrollable area of the page.
The origin#
All transforms happen relative to a point, the transform-origin, which defaults to the center of the element.
.door {
transform-origin: left center; /* hinge on the left edge */
transform: rotate(-30deg);
}
.menu {
transform-origin: top; /* grow downward from the top edge */
transform: scaleY(0);
}
rotate(30deg) with origin at center with origin at top-left
┌──────┐ spins in place ┌──────┐ swings around the corner
│ · │ │· │
└──────┘ └──────┘
Origin changes the whole effect. A dropdown that scales from its top edge looks like it unfolds. The same scale from the center looks like it inflates.
Multiple functions and their order#
You can chain functions in one declaration:
.x { transform: translateX(100px) rotate(45deg); }
.y { transform: rotate(45deg) translateX(100px); }
They are not the same. Read the list from left to right, and each function works in the coordinate system the previous one left behind. After rotate(45deg), the element's x-axis points diagonally, so the translateX(100px) that follows moves it diagonally. In the other order, the element moves 100px to the right first and then rotates around its own origin, ending up in a different place.
Individual transform properties#
Modern browsers also support separate properties:
.card {
translate: 0 -4px;
rotate: 2deg;
scale: 1.05;
}
They compose independently, so you can animate or override one without rewriting the rest of a long transform value. This is very convenient with hover states and with animations, where you want to change only the scale and leave the translation intact.
3D transforms#
.scene { perspective: 800px; }
.card {
transform-style: preserve-3d;
transition: transform 600ms;
}
.card:hover { transform: rotateY(180deg); }
.face { backface-visibility: hidden; }
perspective, set on the parent, defines how strongly depth is rendered. Smaller values make a more dramatic effect.transform-style: preserve-3dkeeps children in 3D space rather than flattening them.backface-visibility: hiddenhides an element when it is turned away from the viewer. A card flip uses two faces, the back one pre-rotated by 180 degrees.
<div class="scene">
<div class="card">
<div class="face front">Front</div>
<div class="face back">Back</div>
</div>
</div>
Side effects of using a transform#
An element with a transform other than none:
- Creates a stacking context. Its
z-indexand those of its children are now contained. - Becomes the containing block for
fixedandabsolutedescendants. Aposition: fixedmodal inside a transformed parent positions itself relative to that parent, not the viewport. - May render text slightly blurry if the transform leaves it at a fractional pixel position. Using even numbers or
translate3dsometimes avoids it, but the cleaner fix is to avoid half pixels. - Can be promoted to its own compositor layer, which is part of why it is fast, and also uses memory.
The will-change: transform hint tells the browser to prepare a layer early. Use it only on elements that really are about to animate, and remove it afterward. Putting it on dozens of elements wastes memory and can make the page slower.
Typical uses#
.card { transition: transform 200ms ease-out; }
.card:hover { transform: translateY(-4px); }
.btn:active { transform: scale(0.97); }
.chevron { transition: transform 200ms; }
.open .chevron { transform: rotate(180deg); }
Common mistakes#
- Using margins or
topto move things in animations instead of a transform. - Forgetting that layout space is unchanged, and being surprised when a scaled-up card overlaps its neighbors.
- Expecting
translatepercentages to be relative to the parent. - Changing
transformorder and getting a different result. - A fixed element that is not fixed, because an ancestor has a transform.
- Overusing
will-change. - Overwriting a
transformon hover and accidentally dropping the base one, such as a centering translate.
Practice#
- Build a card that lifts 4px and gets a larger shadow on hover. Confirm neighbors do not move.
- Make a door that opens by rotating around its left edge by changing
transform-origin. - Swap the order of
translateX(100px) rotate(45deg)and observe the difference. Draw the path each one follows. - Build a flip card with front and back faces. Then put it in a transformed parent with a fixed modal inside and explain the side effect.