The Parallax Effect occurs when two things move at a different speed relative to each other. This effect, usually implemented through scrolling, can provide a seamless user experience to your website and has recently been a popular trend in user interface design.
This effect is implemented in many ways like
- long and fixed position scrolling
- zooming effect
- horizontal transitioning
- moving images at different speeds to create a sense of depth
In this blog, we'll create the Zoom-Out-Slide-In Parallax Effect
combining the first three ways with the help of framer-motion
.
So without further ado, let's get started.
Pre-requisites:
- Basic knowledge of
react
version 16.8+ - Basic knowledge of framer-motion library
We'll break down the animation into 3 stages for the ease of understanding
Create base structure
The structure is simple. We'll create a Main
container, a Parent
container, and 2 Child
containers for the base structure. The Main
container will have a certain height greater than the window size as the Parallax effect is implemented through scrolling. Let's keep its height as 300vh though you can assign it as per your requirements. Its width is 100vw, acquiring the whole window width. The Child
containers are created using motion.div
as they will be animated later. The code for creating the base structure looks as below:
import React from "react";
import { motion } from "framer-motion";
export default function App() {
return (
<div style={{ width: "100vw", height: "300vh" }}>
<div style={{ display: "flex" }}>
<motion.div className="child" style={{ height: "100%", width: "50vw" }}>
<h2>Slide In </h2>
</motion.div>
<motion.div className="child" style={{ height: "100%", width: "50vw" }}>
<h2>Scroll Down to Zoom Out</h2>
</motion.div>
</div>
</div>
);
}
The child
class added in styles.css
helps in laying out the children.
.child {
padding: 2rem;
justify-content: center;
align-items: center;
display: flex;
flex-direction: column;
}
The base structure seems ready.
Create Zoom-Out effect
Now we will implement the Zoom-Out effect on the right child. The basic idea is to scale the right child down from 2x to 1x on the scroll. framer-motion
provides useViewPortScroll() hook that returns MotionValues when viewport scrolls. Since we want to perform scaling animation on a vertical scroll, we will extract scrollY
from it to keep track of vertical scroll distance in pixels. We want to perform a scaling transformation based on the range of scrollY
. For this, we will use the useTransform() hook that transforms and returns the output of a MotionValue based on the input range of another MotionValue.
- We will perform transformation when
scrollY
updates from input values 0px to 500px. - We will create MotionValue
scaleRight
and transform it from output values 2 to 1. - We will also animate its translation value to add movement while scaling. For vertical translation, we will create MotionValue
yRight
and transform it from output values 25vh to 0vh. - For horizontal translation, we will create MotionValue
xRight
and transform it from output values -25vw to 0vw. - We then assign the output MotionValues to the right child
motion.div
for animation.
Note that the input values and output values provided to the useTransform()
hook are not final and can be fine-tuned according to one's requirements.
Next, we want the effect to be finished before the Parent
container scrolls up and goes out of the viewport. To achieve this, let's implement fixed position scrolling and make the Parent
container sticky.
The code for the Zoom-Out effect is as follows:
import React from "react";
import { motion, useTransform, useViewportScroll } from "framer-motion";
export default function App() {
const { scrollY } = useViewportScroll();
const scaleRight = useTransform(scrollY, [0, 500], [2, 1]);
const yRight = useTransform(scrollY, [0, 500], ["25vh", "0vh"]);
const xRight = useTransform(scrollY, [0, 500], ["-25vw", "0vw"]);
return (
<div style={{ width: "100vw", height: "300vh" }}>
<div
style={{
height: "110vh",
display: "flex",
position: "sticky",
top: "0px",
}}
>
<motion.div className="child" style={{ height: "100%", width: "50vw" }}>
<h2>Slide In </h2>
</motion.div>
<motion.div
className="child"
style={{
height: "100%",
width: "50vw",
scale: scaleRight,
y: yRight,
x: xRight,
}}
>
<h2>Scroll Down to Zoom Out</h2>
</motion.div>
</div>
</div>
);
}
The Zoom-Out Effect seems done.
Create Slide-In effect
Now, we will implement the Slide-In
effect on the left child. This will be just a simple horizontal translation interpolated on a vertical scroll.
- Initially, the left child to be out of the viewport and it will gradually slide in the viewport when
scrollY
reaches 500px. - For this let's create MotionValue
xLeft
and transform from output values -60% to 0vw and assign it to left childmotion.div
for animation. You can play around with the input and output values as per your needs.
The code is as follows:
import React from "react";
import { motion, useTransform, useViewportScroll } from "framer-motion";
export default function App() {
const { scrollY } = useViewportScroll();
const scaleRight = useTransform(scrollY, [0, 500], [2, 1]);
const yRight = useTransform(scrollY, [0, 500], ["25vh", "0vh"]);
const xRight = useTransform(scrollY, [0, 500], ["-25vw", "0vw"]);
const xLeft = useTransform(scrollY, [0, 500], ["-60%", "0vw"]);
return (
<div style={{ width: "100vw", height: "300vh" }}>
<div
style={{
height: "110vh",
display: "flex",
position: "sticky",
top: "0px",
}}
>
<motion.div
className="child"
style={{
height: "100%",
width: "50vw",
x: xLeft,
}}
>
<h2>Slide In </h2>
</motion.div>
<motion.div
className="child"
style={{
height: "100%",
width: "50vw",
scale: scaleRight,
y: yRight,
x: xRight,
}}
>
<h2>Scroll Down to Zoom Out</h2>
</motion.div>
</div>
</div>
);
}
Tada! we have implemented the Zoom-Out-Slide-In Parallax effect.
You can find the code here and play around with it. With framer-motion's useViewPortScroll()
and useTransform()
hooks, we can create beautiful and engaging parallax scrolling effects in a simple and effortless manner.