Episode 1 — Fundamentals / 1.12 — CSS Animations and Motion Design
1.12.c — 3D Transforms
In one sentence: CSS 3D transforms add a depth axis (Z) and a camera-like perspective so you can rotate elements in 3D space, build card flips, and create spatial UI — all without JavaScript or WebGL.
Navigation: ← 1.12.b — CSS Transform · 1.12.d — Keyframe Animations →
1. Perspective — the virtual camera
Without perspective, 3D rotations look flat. Perspective controls how "far" the viewer is from the Z = 0 plane.
.scene {
perspective: 800px;
}
perspective: 200px (close) perspective: 1000px (far away)
╱─────────────╲ ┌───────────────┐
╱ ╲ │ │
╱ extreme ╲ │ subtle │
╲ foreshortening╱ │ foreshortening│
╲ ╱ │ │
╲─────────────╱ └───────────────┘
| Approach | Syntax | Scope |
|---|---|---|
| Parent property | perspective: 800px on the container | Shared vanishing point for all children |
| Function in transform | transform: perspective(800px) rotateY(45deg) | Per-element; each element has its own vanishing point |
Tip: for multiple cards in a grid, use
perspectiveon the parent so they share one natural vanishing point.
perspective-origin
Moves the vanishing point (default: center of the container):
.scene {
perspective: 800px;
perspective-origin: left top;
}
2. 3D translate
| Function | Axis |
|---|---|
translateZ(value) | Toward / away from viewer |
translate3d(x, y, z) | All three axes at once |
.popup {
transform: translateZ(60px); /* comes toward viewer */
}
Positive Z = closer (appears larger under perspective). Negative Z = farther (appears smaller).
3. 3D rotate
| Function | What it does |
|---|---|
rotateX(angle) | Tip forward/backward (like a garage door) |
rotateY(angle) | Spin left/right (like a revolving door) |
rotateZ(angle) | Same as 2D rotate() — spin in-plane |
rotate3d(x, y, z, angle) | Rotate around an arbitrary axis vector |
rotateX(45deg) rotateY(45deg) rotateZ(45deg)
─── │ ╲
╱ ╲ ╱│ ╲───┐
│ │ ╱ │ │ │
│ │ ╱ │ │ │
╲ ╱ │ ┌───╱
─── │ ╱
(tips toward you) (turns like a door) (flat spin, same as 2D)
4. transform-style: preserve-3d
By default, child transforms are flattened into the parent's plane. To let children live in true 3D space:
.card-inner {
transform-style: preserve-3d;
transition: transform 600ms ease;
}
Without preserve-3d, a child rotated on a different axis than its parent will look flat — it won't "pop out" of the parent's plane.
5. backface-visibility
When an element rotates past 90°, its back face becomes visible. Control this:
.card-face {
backface-visibility: hidden; /* hide the back when facing away */
}
| Value | Behavior |
|---|---|
visible (default) | Back face is shown (mirrored content) |
hidden | Element is invisible when facing away from the viewer |
6. Classic card-flip animation
<div class="card-scene">
<div class="card">
<div class="card__front">Front</div>
<div class="card__back">Back</div>
</div>
</div>
.card-scene {
perspective: 600px;
width: 300px;
height: 200px;
}
.card {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
transition: transform 600ms ease;
}
.card-scene:hover .card {
transform: rotateY(180deg);
}
.card__front,
.card__back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 8px;
}
.card__front {
background: #3b82f6;
color: white;
}
.card__back {
background: #1e293b;
color: white;
transform: rotateY(180deg);
}
How it works step-by-step
Initial state After hover (rotateY 180°)
┌───────────┐ ┌───────────┐
│ │ │ │
│ FRONT │ ──rotateY──► │ BACK │
│ (facing) │ (180deg) │ (facing) │
│ │ │ │
└───────────┘ └───────────┘
Front: backface visible Front: backface hidden (gone)
Back: rotateY(180°) hidden Back: now at 360° total (visible)
.cardusespreserve-3dso both faces live in 3D.- Both faces use
backface-visibility: hidden. .card__backstarts pre-rotated 180° (facing away).- On hover, the whole
.cardrotates 180° — front hides, back reveals.
7. 3D carousel concept
.carousel {
transform-style: preserve-3d;
}
.carousel__item:nth-child(1) { transform: rotateY( 0deg) translateZ(250px); }
.carousel__item:nth-child(2) { transform: rotateY( 60deg) translateZ(250px); }
.carousel__item:nth-child(3) { transform: rotateY(120deg) translateZ(250px); }
.carousel__item:nth-child(4) { transform: rotateY(180deg) translateZ(250px); }
.carousel__item:nth-child(5) { transform: rotateY(240deg) translateZ(250px); }
.carousel__item:nth-child(6) { transform: rotateY(300deg) translateZ(250px); }
Each item rotates to its slice of 360° then pushes outward on Z. Rotate the parent to spin the carousel.
8. Performance notes
- 3D transforms promote elements to their own compositor layer (GPU texture).
- Too many layers = high GPU memory. A page with hundreds of
preserve-3dchildren can stutter. - Keep 3D transforms to focal elements: cards, modals, hero sections — not every list item.
- Test on low-end devices; integrated GPUs have limited VRAM.
9. Key takeaways
perspectivecreates depth — set it on the parent for shared vanishing point.rotateX/Y/Zspin elements in 3D;translateZmoves toward/away from the viewer.preserve-3dlets nested elements exist in shared 3D space.backface-visibility: hiddenis essential for card-flip patterns.- Use 3D transforms sparingly — they consume GPU memory per promoted layer.
Explain-It Challenge
Explain without notes:
- The difference between
perspective: 800pxon a parent vstransform: perspective(800px)on the element itself — when does each produce different results? - Why both card faces need
backface-visibility: hiddenand why the back face starts withrotateY(180deg). - Why a carousel with 50 items using
preserve-3dmight perform poorly on a mobile device.
Navigation: ← 1.12.b — CSS Transform · 1.12.d — Keyframe Animations →