Parallax Effects
When to Use
Parallax creates the illusion of depth by moving foreground and background elements at different speeds during scroll. Use sparingly — one or two parallax layers on a hero section, not every card on the page. Most effective for marketing heroes, editorial pages, and portfolio pieces.
Decision
| If you need... | Use... | Why |
|---|---|---|
| CSS-only parallax with no JS | perspective + translateZ technique |
Compositor thread, zero JS, tied to scroll machinery |
| Per-element scroll-speed control | animation-timeline: scroll() with custom keyframes |
Fine-grained control over each layer's travel distance |
| Background-only parallax | background-attachment: fixed |
Simplest approach; avoid on iOS Safari (broken) |
| Mouse-driven depth effect | JS CSS variables + rotateX/Y |
See 3D Transforms instead |
| Disable on mobile | Media query reducing translateZ to 0 |
Mobile viewports too narrow; effect feels claustrophobic |
Professional vs Cheap Parallax
| Quality | Speed Ratio | Number of Layers | Notes |
|---|---|---|---|
| Professional | 0.3–0.6x | 2-3 max | Subtle depth, not disorienting |
| Acceptable | 0.1–0.8x | 2-4 | Fine for hero sections |
| Cheap/nauseating | >1x (reverse parallax) | 5+ | Causes vestibular issues |
| Invisible | ~0.9x | Any | Not worth the performance cost |
Pattern
Perspective container technique — elements pushed back in Z-space scroll slower because the browser applies perspective math to scroll matrices:
.parallax-viewport {
height: 100vh;
overflow-x: hidden;
overflow-y: scroll;
perspective: 1px;
perspective-origin: 0 0;
}
.parallax-section {
position: relative;
transform-style: preserve-3d;
}
/* Background layer — pushed back, scrolls at ~0.33x speed */
.parallax-bg {
transform: translateZ(-2px) scale(3); /* scale = (perspective - distance) / perspective */
}
/* Foreground layer — stays at normal scroll speed */
.parallax-fg {
transform: translateZ(0);
}
Scale compensation formula: scale = (perspective - translateZ) / perspective. With perspective: 1px and translateZ(-2px): scale = (1 - (-2)) / 1 = 3. Without scale compensation, elements shrink as they move back in Z-space.
Scroll-driven parallax (modern approach):
@keyframes parallax-bg {
from { transform: translateY(0); }
to { transform: translateY(-30%); }
}
.hero-bg {
animation: parallax-bg linear both;
animation-timeline: scroll(root);
}
@supports not (animation-timeline: scroll()) {
.hero-bg { transform: none; }
}
Feature reference: See modern-css → scroll-driven-animations for
animation-timelinesyntax.
Accessibility and mobile:
@media (prefers-reduced-motion: reduce) {
.parallax-bg,
.parallax-viewport {
perspective: none;
transform: none;
animation: none;
}
}
/* iOS Safari fix: background-attachment: fixed doesn't work */
@media (max-width: 768px) {
.parallax-bg {
transform: none;
animation: none;
}
}
Common Mistakes
- Forgetting
transform-style: preserve-3don the section container — flattens the 3D space, killing the effect - Any element between the perspective container and the parallax child — intermediate containers with their own transforms flatten the perspective
background-attachment: fixedon iOS — does not work; use thetranslateZtechnique or scroll-driven instead- Too many parallax layers (5+) — creates visual noise and performance cost; 2-3 is the effective maximum
- Not applying scale compensation — background elements shrink as they're pushed back in Z-space, causing transparent edges
- No
prefers-reduced-motionfallback — vestibular disorder users will feel sick; this is a WCAG 2.3.3 violation
See Also
- Entrance Animations — scroll-triggered reveals vs continuous parallax
- Animation Performance — why perspective-based parallax stays compositor-safe
- Accessibility and Motion — parallax is a primary vestibular trigger
- Reference: Chrome: Performant Parallaxing
- Reference: CSS-Tricks: Scroll-Driven Parallax