Parallax Effects
When to Use
Use parallax for marketing heroes, editorial pages, and portfolio pieces — one or two layers max. Use CSS perspective technique for compositor-safe zero-JS parallax; use scroll-driven for modern browsers with fine-grained control. Do not apply to every card on a page.
Decision
| Situation | Choose | 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 |
| 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:
.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 — scrolls at ~0.33x speed */
.parallax-bg { transform: translateZ(-2px) scale(3); }
/* scale = (perspective - translateZ) / perspective = (1 - (-2)) / 1 = 3 */
.parallax-fg { transform: translateZ(0); }
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; } }
Accessibility and mobile:
@media (prefers-reduced-motion: reduce) {
.parallax-bg, .parallax-viewport { perspective: none; transform: none; animation: none; }
}
@media (max-width: 768px) {
.parallax-bg { transform: none; animation: none; } /* iOS Safari fix */
}
Common Mistakes
- Wrong: Forgetting
transform-style: preserve-3don the section — Right: Without it, 3D space flattens and kills the effect - Wrong: Intermediate containers with their own transforms — Right: Any element between perspective container and parallax child flattens the perspective
- Wrong:
background-attachment: fixedon iOS — Right: Does not work on iOS; usetranslateZtechnique or scroll-driven instead - Wrong: 5+ parallax layers — Right: 2-3 is the effective maximum; more creates visual noise and performance cost
- Wrong: No scale compensation — Right: Background elements shrink as they move back in Z-space, causing transparent edges
- Wrong: No
prefers-reduced-motionfallback — Right: WCAG 2.3.3 violation; vestibular disorder users will feel sick
See Also
- Entrance Animations — scroll-triggered reveals vs continuous parallax
- Animation Performance — 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