Parallax Effect with Framer Motion
Published
· 2 years ago
framer motionparallaxreactanimationtypescript
Framer Motion is motion library for React that makes it easy to add beautiful animation to your apps.
In this article, we'll see how to create a parallax effect with Framer Motion in a few lines of code. We will be using Vite for tooling and TailwindCSS for styling, but you can use any build tool and any CSS framework or no framework at all.
In this article, we'll see how to create a parallax effect with Framer Motion in a few lines of code. We will be using Vite for tooling and TailwindCSS for styling, but you can use any build tool and any CSS framework or no framework at all.
What is a Parallax Effect?
A parallax effect is a visual effect where elements on the page move at different speeds when the user scrolls, which can give the illusion of depth. A great example of this is the Firewatch game website.

The example we will be creating is much simpler and not as impressive as this one, but it should give you a good idea of how the parallax effect works and how to implement it.
Setting up the Project
We will be using a simple Vite project for this tutorial.
We start by creating a new Vite project with the following command:
We start by creating a new Vite project with the following command:
# npm 6.x
npm create vite@latest parallax --template react-ts
# npm 7+, extra double-dash is needed:
npm create vite@latest parallax -- --template react-ts
cd parallax
npm install# npm 6.x
npm create vite@latest parallax --template react-ts
# npm 7+, extra double-dash is needed:
npm create vite@latest parallax -- --template react-ts
cd parallax
npm installThen, install Framer Motion and Tailwind CSS:
npm install framer-motion
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -pnpm install framer-motion
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -pNext, replace the contents of
/src/index.css with the following:/src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;/src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;And the contents of
tailwind.config.js with the following:tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};Creating the Parallax Effect
Let's create a new component file called
parallax.tsx inside /src/components and add the following code:/src/components/parallax.tsx
import { motion } from 'framer-motion';
export default function Parallax() {
return (
<motion.div className="flex h-1/2 w-2/3 flex-row gap-2">
<motion.div className="w-1/3 bg-rose-600" />
<motion.div className="w-1/3 bg-amber-600" />
<motion.div className="w-1/3 bg-emerald-600" />
</motion.div>
);
}/src/components/parallax.tsx
import { motion } from 'framer-motion';
export default function Parallax() {
return (
<motion.div className="flex h-1/2 w-2/3 flex-row gap-2">
<motion.div className="w-1/3 bg-rose-600" />
<motion.div className="w-1/3 bg-amber-600" />
<motion.div className="w-1/3 bg-emerald-600" />
</motion.div>
);
}This is a simple component that renders three boxes with different colors.
We are using
<motion.div><motion.div> instead of <div><div> so we can later add Framer Motion properties to these elements.Now lets head to
/src/App.tsx and place our new component inside the <App><App> component:/src/App.tsx
import Parallax from './components/parallax';
function App() {
return (
<>
<section className="flex h-screen w-screen items-center justify-center">
<Parallax />
</section>
<section className="flex h-screen w-screen items-center justify-center">
<Parallax />
</section>
</>
);
}
export default App;/src/App.tsx
import Parallax from './components/parallax';
function App() {
return (
<>
<section className="flex h-screen w-screen items-center justify-center">
<Parallax />
</section>
<section className="flex h-screen w-screen items-center justify-center">
<Parallax />
</section>
</>
);
}
export default App;We created 2 sections, each taking up the full width and height of the screen and placed our
Our app should look like this now:
<Parallax><Parallax> component inside each one of them.Our app should look like this now:

Now let's add the parallax effect to our component.
We want the red box to move as normal as we scroll, the yellow box to move faster and the green box to move even faster.
We want the red box to move as normal as we scroll, the yellow box to move faster and the green box to move even faster.
To achieve this, we need to track the scroll progress of the wrapper div of our
<Parallax><Parallax> component. We can do this using the useScrolluseScroll hook from Framer Motion:/src/components/parallax.tsx
import { useRef } from 'react';
import { motion, useScroll } from 'framer-motion';
export default function Parallax() {
const ref = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: ref });
return (
<motion.div ref={ref} className="flex h-1/2 w-2/3 flex-row gap-2">
<motion.div className="w-1/3 bg-rose-600" />
<motion.div className="w-1/3 bg-amber-600" />
<motion.div className="w-1/3 bg-emerald-600" />
</motion.div>
);
}/src/components/parallax.tsx
import { useRef } from 'react';
import { motion, useScroll } from 'framer-motion';
export default function Parallax() {
const ref = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: ref });
return (
<motion.div ref={ref} className="flex h-1/2 w-2/3 flex-row gap-2">
<motion.div className="w-1/3 bg-rose-600" />
<motion.div className="w-1/3 bg-amber-600" />
<motion.div className="w-1/3 bg-emerald-600" />
</motion.div>
);
}We created a
Now
refref for the wrapper div and passed it to the useScrolluseScroll hook as the target element.Now
scrollYProgressscrollYProgress tracks the scroll progress of the wrapper div: when it's at the top of the screen, the value is 0, and the value will transition to 1 as it reaches the bottom of the screen.Next, we define a function called
useParallaxuseParallax:/src/components/parallax.tsx
function useParallax(value: MotionValue<number>, distance: number) {
return useTransform(value, [0, 1], [-distance, distance]);
}/src/components/parallax.tsx
function useParallax(value: MotionValue<number>, distance: number) {
return useTransform(value, [0, 1], [-distance, distance]);
}To understand what this function does, we need to understand what the
useTransformuseTransform hook does.function useTransform<I, O>(
value: MotionValue<number>,
inputRange: InputRange,
outputRange: O[],
options?: TransformOptions<O>,
): MotionValue<O>;function useTransform<I, O>(
value: MotionValue<number>,
inputRange: InputRange,
outputRange: O[],
options?: TransformOptions<O>,
): MotionValue<O>;useTransformuseTransform creates a MotionValueMotionValue that transforms the output of another MotionValueMotionValue. A motion value can be opacityopacity, scalescale, xx, yy, etc.In the following example, the value of
opacityopacity will be 1 when xx is 0, transition to 0 when xx is 100 and transition back to 1 when xx is 200.export const MyComponent = () => {
const x = useMotionValue(0);
const opacity = useTransform(x, [0, 100, 200], [1, 0, 1]);
return <motion.div style={{ x, opacity }} />;
};export const MyComponent = () => {
const x = useMotionValue(0);
const opacity = useTransform(x, [0, 100, 200], [1, 0, 1]);
return <motion.div style={{ x, opacity }} />;
};In our
When
useParallaxuseParallax function, we are using useTransformuseTransform to transform the valuevalue passed to the function.When
valuevalue is 0, the output will be -distance, and it will transition to distance when value is 1.The value we are going to pass to
useParallaxuseParallax is the scrollYProgressscrollYProgress of the wrapper div:/src/components/parallax.tsx
const yYellow = useParallax(scrollYProgress, 300);
const yGreen = useParallax(scrollYProgress, 600);/src/components/parallax.tsx
const yYellow = useParallax(scrollYProgress, 300);
const yGreen = useParallax(scrollYProgress, 600);This means that:
- When the wrapper div is at the top of the screen,
scrollYProgressscrollYProgressis0, thereforeyYellowyYellowandyGreenyGreenwill be-300and-600respectively. - When the wrapper div is at the middle of the screen,
scrollYProgressscrollYProgressis0.5, thereforeyYellowyYellowandyGreenyGreenwill both be0. - When the wrapper div is at the bottom of the screen,
scrollYProgressscrollYProgressis1, thereforeyYellowyYellowandyGreenyGreenwill be300and600respectively.
All we have to do now is pass the
yYellowyYellow and yGreenyGreen values to the y property of the yellow and green boxes respectively:/src/components/parallax.tsx
export default function Parallax() {
// ...
return (
<motion.div ref={ref} className="flex h-1/2 w-2/3 flex-row gap-2">
<motion.div className="w-1/3 bg-rose-600" />
<motion.div
className="w-1/3 bg-amber-600"
initial={{ y: 0 }}
style={{ y: yYellow }}
/>
<motion.div
className="w-1/3 bg-emerald-600"
initial={{ y: 0 }}
style={{ y: yGreen }}
/>
</motion.div>
);
}/src/components/parallax.tsx
export default function Parallax() {
// ...
return (
<motion.div ref={ref} className="flex h-1/2 w-2/3 flex-row gap-2">
<motion.div className="w-1/3 bg-rose-600" />
<motion.div
className="w-1/3 bg-amber-600"
initial={{ y: 0 }}
style={{ y: yYellow }}
/>
<motion.div
className="w-1/3 bg-emerald-600"
initial={{ y: 0 }}
style={{ y: yGreen }}
/>
</motion.div>
);
}We also set the inital value of
yy to 0 so the boxes are positioned correctly when the page loads.That's it! We have successfully added a parallax effect to our component.

Here is the complete code for the
<Parallax><Parallax> component:/src/components/parallax.tsx
import { useRef } from 'react';
import {
motion,
useScroll,
useTransform,
type MotionValue,
} from 'framer-motion';
function useParallax(value: MotionValue<number>, distance: number) {
return useTransform(value, [0, 1], [-distance, distance]);
}
export default function Parallax() {
const ref = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: ref });
const yYellow = useParallax(scrollYProgress, 300);
const yGreen = useParallax(scrollYProgress, 600);
return (
<motion.div ref={ref} className="flex h-1/2 w-2/3 flex-row gap-2">
<motion.div className="w-1/3 bg-rose-600" />
<motion.div
className="w-1/3 bg-amber-600"
initial={{ y: 0 }}
style={{ y: yYellow }}
/>
<motion.div
className="w-1/3 bg-emerald-600"
initial={{ y: 0 }}
style={{ y: yGreen }}
/>
</motion.div>
);
}/src/components/parallax.tsx
import { useRef } from 'react';
import {
motion,
useScroll,
useTransform,
type MotionValue,
} from 'framer-motion';
function useParallax(value: MotionValue<number>, distance: number) {
return useTransform(value, [0, 1], [-distance, distance]);
}
export default function Parallax() {
const ref = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: ref });
const yYellow = useParallax(scrollYProgress, 300);
const yGreen = useParallax(scrollYProgress, 600);
return (
<motion.div ref={ref} className="flex h-1/2 w-2/3 flex-row gap-2">
<motion.div className="w-1/3 bg-rose-600" />
<motion.div
className="w-1/3 bg-amber-600"
initial={{ y: 0 }}
style={{ y: yYellow }}
/>
<motion.div
className="w-1/3 bg-emerald-600"
initial={{ y: 0 }}
style={{ y: yGreen }}
/>
</motion.div>
);
}