Implementing Parallax Scrolling in React Applications
Parallax scrolling creates a visually engaging effect where background elements move slower than foreground elements as users scroll. Here’s how to implement high-performance parallax effects in React.
1. Basic Parallax Implementation
Using React Spring (Recommended)
import { useScroll } from '@react-spring/web';
function ParallaxComponent() {
const { scrollYProgress } = useScroll();
return (
<div className="container">
<animated.div
style={{
transform: scrollYProgress.to(v => `translateY(${v * 100}px)`),
}}
className="parallax-background"
/>
<div className="content">
{/* Normal scrolling content */}
</div>
</div>
);
}
CSS-Only Fallback
function SimpleParallax() {
return (
<div className="parallax-container">
<div className="parallax-background" />
<div className="parallax-content">
{/* Your content here */}
</div>
</div>
);
}
/* CSS */
.parallax-container {
position: relative;
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
}
.parallax-background {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 200%;
background-image: url('your-image.jpg');
background-attachment: fixed;
background-position: center;
background-size: cover;
z-index: -1;
}
2. Layered Parallax Effect
Multiple Parallax Layers
import { useScroll, animated } from '@react-spring/web';
function LayeredParallax() {
const { scrollYProgress } = useScroll();
return (
<div className="scene">
<animated.div
className="layer layer-1"
style={{
transform: scrollYProgress.to(v => `translateY(${v * 30}px)`),
}}
/>
<animated.div
className="layer layer-2"
style={{
transform: scrollYProgress.to(v => `translateY(${v * 60}px)`),
}}
/>
<animated.div
className="layer layer-3"
style={{
transform: scrollYProgress.to(v => `translateY(${v * 90}px)`),
}}
/>
<div className="content">
{/* Page content */}
</div>
</div>
);
}
/* CSS */
.scene {
position: relative;
height: 500vh; /* Extra space for scrolling */
}
.layer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
will-change: transform;
}
.layer-1 {
background: url('far-background.jpg') center/cover;
}
.layer-2 {
background: url('mid-background.png') center/cover;
}
.layer-3 {
background: url('foreground.png') center/cover;
}
.content {
position: relative;
min-height: 100vh;
}
3. Performance-Optimized Parallax
Using Intersection Observer
import { useEffect, useRef, useState } from 'react';
function OptimizedParallax() {
const [scrollY, setScrollY] = useState(0);
const parallaxRef = useRef(null);
useEffect(() => {
const handleScroll = () => {
if (parallaxRef.current) {
const rect = parallaxRef.current.getBoundingClientRect();
const offset = window.pageYOffset - rect.top;
setScrollY(offset);
}
};
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
window.addEventListener('scroll', handleScroll, { passive: true });
} else {
window.removeEventListener('scroll', handleScroll);
}
});
},
{ threshold: 0.1 }
);
if (parallaxRef.current) {
observer.observe(parallaxRef.current);
}
return () => {
window.removeEventListener('scroll', handleScroll);
if (parallaxRef.current) {
observer.unobserve(parallaxRef.current);
}
};
}, []);
return (
<div ref={parallaxRef} className="parallax-container">
<div
className="parallax-element"
style={{
transform: `translateY(${scrollY * 0.5}px)`,
}}
/>
</div>
);
}
4. Horizontal Parallax Scrolling
Horizontal Motion Effect
import { useScroll, animated } from '@react-spring/web';
function HorizontalParallax() {
const { scrollYProgress } = useScroll();
return (
<div className="horizontal-container">
<animated.div
className="moving-element"
style={{
transform: scrollYProgress.to(v => `translateX(${v * 200}px)`),
}}
/>
<div className="long-content">
{/* Content that makes the page scrollable */}
</div>
</div>
);
}
5. Parallax with React Parallax Library
Using react-parallax
import { Parallax, ParallaxLayer } from '@react-spring/parallax';
function FullPageParallax() {
return (
<Parallax pages={3}>
<ParallaxLayer offset={0} speed={0.5}>
<h1>First Page</h1>
</ParallaxLayer>
<ParallaxLayer offset={1} speed={0.2}>
<div className="background" />
</ParallaxLayer>
<ParallaxLayer offset={1} speed={0.5}>
<h2>Second Page</h2>
</ParallaxLayer>
<ParallaxLayer offset={2} speed={0.8}>
<h2>Third Page</h2>
</ParallaxLayer>
</Parallax>
);
}
6. 3D Parallax Effect
Using perspective transforms
function Parallax3D() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e) => {
const x = (e.clientX - window.innerWidth / 2) / 20;
const y = (e.clientY - window.innerHeight / 2) / 20;
setPosition({ x, y });
};
return (
<div
className="parallax-3d-container"
onMouseMove={handleMouseMove}
>
<div
className="parallax-3d-element"
style={{
transform: `rotateX(${position.y}deg) rotateY(${position.x}deg)`,
transition: 'transform 0.1s ease-out',
}}
/>
</div>
);
}
/* CSS */
.parallax-3d-container {
perspective: 1000px;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.parallax-3d-element {
width: 300px;
height: 300px;
background: linear-gradient(45deg, #ff3366, #ba265d);
transform-style: preserve-3d;
will-change: transform;
}
7. Mobile-Friendly Parallax
Device Orientation Detection
function AdaptiveParallax() {
const [isMobile, setIsMobile] = useState(false);
const { scrollYProgress } = useScroll();
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768 ||
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent));
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
return (
<div className="parallax-container">
{!isMobile ? (
<animated.div
className="parallax-element"
style={{
transform: scrollYProgress.to(v => `translateY(${v * 100}px)`),
}}
/>
) : (
<div className="static-element" />
)}
<div className="content">
{/* Your content here */}
</div>
</div>
);
}
Best Practices for Parallax in React
- Performance Optimization:
- Use
will-change: transform
in CSS - Debounce scroll events
- Implement intersection observers to activate effects only when visible
- Accessibility Considerations:
- Provide toggle to disable motion for users with vestibular disorders
- Ensure content remains readable during animations
- Maintain proper contrast ratios
- Mobile Considerations:
- Disable or simplify effects on mobile devices
- Use lighter assets for mobile
- Test performance on low-end devices
- Fallbacks:
- Provide static layouts when JavaScript is disabled
- Use CSS-only parallax as a progressive enhancement
- Gracefully degrade on unsupported browsers
- Asset Optimization:
- Compress images and videos
- Use modern formats like WebP
- Implement lazy loading for offscreen assets
By implementing these parallax techniques in React, you can create immersive scrolling experiences while maintaining performance and accessibility. Choose the approach that best fits your project’s requirements and always test across different devices and browsers.