Parallax scrolling is a popular web design trend that creates a sense of depth by moving background and foreground elements at different speeds. It’s visually engaging and relatively simple to implement in React. In this tutorial, we’ll build a basic parallax effect step-by-step and make it reusable and easy to use. What You’ll Learn • How the parallax effect works • How to implement it using React • Reusable parallax provider Prerequisites • Basic knowledge of React and CSS • Node.js installed on your system • A text editor (e.g., VSCode) 1\. Set Up Your React Project If you don’t already have a React project, create one with the following command: yarn create vite parallax-effect-example --template react-ts
cd parallax-effect-example
yarn install Then, start the development server: yarn dev Then open in the browser: http://localhost:5173/ 2\. Install Dependencies We’ll use the d3-scale library to simplify our implementation. Install it with: yarn add d3-scale
yarn add -D @types/d3-scale 3\. Basic Project Structure Create the following file structure for the project: src/
components/
ParallaxSection.tsx
SectionProvider.tsx
Shape.tsx
App.tsx
index.css 4\. Create Section Provider Create a section provider that will pass props to the parallax section. We need to get the window height and section position using useEffect. const [myPosY, setMyPosY] = useState<number>( 0 )
const [winHeight, setWinHeight] = useState<number>( 0 )
const sectionRef = useRef<HTMLDivElement>( null )
useEffect( () => {
// Add event listener when component mounts
setWinHeight( window.innerHeight )
window.addEventListener( 'scroll', handleScroll )
// Cleanup function to remove event listener
return () => {
window.removeEventListener( 'scroll', handleScroll )
}
}, [] )
function handleScroll() {
if ( sectionRef.current ) {
const { top } = sectionRef.current?.getBoundingClientRect() as DOMRect
setMyPosY( top )
}
} Next, we need to validate if the Section provider has valid children, and then pass props to the children. This way, the child elements will always receive the myposy and winheight props. <div ref={sectionRef}>
{React.Children.map( children, ( child ) => {
if ( React.isValidElement( child ) ) {
return React.cloneElement( child, {
myposy : myPosY,
winheight : winHeight,
} ) // Passing myPosY to React child components
}
return child
} )}
</div> 5\. Create a Parallax Section Component We can use the myposy props to create the parallax effect. The parallax effect is achieved by dynamically adjusting the position and opacity of elements based on the vertical scroll position (myposy). Here's a breakdown of how it works, using the d3-scale library to control the transformations: import { FunctionComponent, useRef } from 'react'
import { scalePow } from 'd3-scale'
import Shape from './Shape'
interface Props {
myposy?: number
winheight?: number
}
const ParallaxSection: FunctionComponent<Props> = ({ myposy }) => {
const ref = useRef<HTMLElement>(null)
const translate = scalePow().domain([-2000, 2000]).range([-100, 100])
const opacity = scalePow()
.domain([
0,
ref.current?.offsetHeight ? ref.current?.offsetHeight - 40 : 1000,
])
.range([1, 0])
return (
<section
ref={ref}
id="home"
className="mainsection"
style={{
opacity: opacity.exponent(1)(myposy ? -myposy : 1),
}}
>
<Shape myposy={myposy || 0} />
<div className="banner-image">
<div
className="avatar-wrapper"
style={{
transform: `translateY(${translate.exponent(1)(
myposy ? -myposy : 0
)}px)`,
}}
>
<div className="avatar"></div>
</div>
</div>
<div
className="profile-content"
style={{
transform: `translateY(${translate.exponent(1)(
myposy ? -myposy : 0
)}px)`,
}}
>
<h1 className="profile-title">This is Parallax</h1>
<div className="profile-bio">
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry's standard dummy text
ever since the 1500s, when an unknown printer took a galley of type
and scrambled it to make a type specimen book. It has survived not
only five centuries, but also the leap into electronic typesetting,
remaining essentially unchanged. It was popularised in the 1960s
with the release of Letraset sheets containing Lorem Ipsum passages,
and more recently with desktop publishing software like Aldus
PageMaker including versions of Lorem Ipsum.
</p>
</div>
</div>
</section>
)
}
export default ParallaxSection 6\. Update Your App Component Import and use the ParallaxSection component in App.js: import SectionProvider from './components/SectionProvider'
import ParallaxSection from './components/ParallaxSection'
function App() {
return (
<main>
<SectionProvider>
<ParallaxSection />
</SectionProvider>
<section
className="mainsection"
style={{
height: '1000px',
backgroundColor: 'var(--dark-secondary)',
}}
></section>
</main>
)
}
export default App 7\. Add Styling Delete your app.css and change all the index.css with code below: :root {
--dark: #222222;
--dark-secondary: #393939;
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#root {
width: 100vw;
}
body {
margin: 0;
display: flex;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
.mainsection {
padding: 0 !important;
background-color: var(--dark);
position: relative;
margin-inline: auto;
overflow: hidden;
}
.banner-image {
display: flex;
width: 100%;
height: 14rem;
position: relative;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-color: rgb(159, 159, 159);
}
.avatar-wrapper {
position: absolute;
bottom: -6rem;
left: 0px;
right: 0px;
margin-inline: auto;
width: 12rem;
aspect-ratio: 1;
border: none;
border-radius: 50%;
overflow: hidden;
}
.avatar {
background-color: var(--dark-secondary);
width: 100%;
height: 100%;
}
.profile-content {
width: 100%;
max-width: 48rem;
margin: 8rem auto 5rem;
display: flex;
flex-direction: column;
gap: 1rem;
padding: 0 1rem;
overflow: hidden;
}
.profile-title {
text-align: center;
margin: 0;
}
.profile-bio {
text-align: center;
color: rgba(255, 255, 255, 0.75);
}
.social-links .dot {
display: inline-block;
}
.last-hidden:last-child {
display: none;
}
/* Shape */
.shape {
position: absolute;
}
.shape-1 {
width: 40px;
height: 40px;
background-color: var(--dark-secondary);
top: 80%;
left: 6rem;
}
.shape-2 {
width: 0;
height: 0;
top: 10%;
left: 12rem;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-bottom: 30px solid var(--dark-secondary);
}
.shape-3 {
width: 0;
height: 0;
top: 25%;
left: 75%;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-bottom: 30px solid var(--dark-secondary);
}
.shape-4 {
width: 40px;
height: 40px;
background-color: var(--dark-secondary);
top: 66%;
left: 50%;
}
.shape-5 {
width: 40px;
height: 40px;
background-color: var(--dark-secondary);
top: 85%;
left: 90%;
}
.shape-6 {
width: 0;
height: 0;
top: 60%;
left: 50%;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-bottom: 30px solid var(--dark-secondary);
} 8\. Test the Parallax Effect Run your app with: yarn dev Scroll through the page, and you’ll see the heading and paragraph move at different speeds, creating a parallax effect. 8\. Customize and Enhance Here are some ideas to enhance the effect: • Add images: Replace text with parallax-enabled images. • Multiple sections: Add more Parallax components with varying speeds. • Performance optimization: Use lazy loading for images and limit the number of layers. Live Demo Here’s a live demo on CodeSandbox: [Parallax Effect in React](https://codesandbox.io/p/github/ianfebi01/parallax-effect-example/main) Full Code You can fork this repo to start experimenting with the parallax effect! On [GitHub](https://github.com/ianfebi01/parallax-effect-example.git) Conclusion You’ve successfully implemented a parallax effect in React! This technique can make your website more dynamic and engaging. Experiment with different speeds and elements to create unique designs.