How to implement smooth scrolling in Next.js with Lenis and GSAP
In an era where engaging and interactive web pages are the norm, understanding and implementing interactive features can significantly impact how users interact with your content. In this blog, we dive into implementing smooth scrolling and parallax effects, two pivotal elements that can elevate your website’s user experience to new heights.
We’re focusing on Next.js, a powerful React framework, and leveraging the capabilities of Lenis and GSAP (GreenSock Animation Platform) to bring these effects to life. Whether you’re a seasoned developer or just starting out, this tutorial aims to help you with the skills needed to integrate smooth scrolling and parallax effects seamlessly into your Next.js projects.
Here is the demo that we are going to create: Smooth scroll in Next.js
If you prefer to watch the video here is the video tutorial:
What is Smooth Scrolling and Parallax Effects?
Smooth Scrolling: This is more than just a visual delight; it’s about enhancing the user’s journey through your website. Smooth scrolling provides a controlled, gradual navigation between sections of your webpage, rather than the abrupt jumps typical of traditional page scrolling.
It’s not just about aesthetics; it’s about creating a narrative flow that guides your users through your content in a way that feels natural and engaging.
Parallax Effect: Imagine layers of content moving at different speeds as you scroll, creating an illusion of depth and immersion. That’s the parallax effect for you.
It adds a 3D feel to your 2D web pages, making the user experience more interactive and captivating. When used sensibly, it can turn a simple scroll through your website into a story-like experience, keeping users intrigued and engaged. To bring these concepts to life in a Next.js environment, we utilize Lenis and GSAP.
-
Lenis is a minimalistic library designed for smooth scrolling, offering a simple yet effective way to implement this feature.
-
GSAP is a robust tool for creating high-performance animations, including the parallax effect. Together, they form a formidable duo to enhance your Next.js projects. Now let’s get started.
Setting Up the Next.js Environment
Before diving into the implementation, let’s set up our Next.js environment. First, ensure you have Node.js installed on your system. Open your terminal and run the following command:
npx create-next-app@latest
Once you run this command it will ask you few things which you can select as per the following selection:
√ What is your project named? ... nextjs-smooth-scroll
√ Would you like to use TypeScript? ... No
√ Would you like to use ESLint? ... Yes
√ Would you like to use Tailwind CSS? ... Yes
√ Would you like to use `src/` directory? ... Yes
√ Would you like to use App Router? (recommended) ... Yes
? Would you like to customize the default import alias (@/*)? » No
Once it’s done installing all the required dependencies, navigate into your project directory using cd nextjs-smooth-scoll
and run the following command to install the gsap and lenis scroll libraries.
npm install gsap @studio-freight/react-lenis
Now Run npm run dev
to start the development server. You can view your project at http://localhost:3000.
Implementing Smooth Scroll with Lenis
In your Next.js project, create a file named SmoothScrolling.jsx
in the components
folder. This component utilizes @studio-freight/react-lenis
, a React wrapper for the Lenis library. You can learn more about it from it’s documentation.
1"use client";
2import { ReactLenis } from "@studio-freight/react-lenis";
3
4function SmoothScrolling({ children }) {
5 return (
6 <ReactLenis root options={{ lerp: 0.1, duration: 1.5, smoothTouch: true }}>
7 {children}
8 </ReactLenis>
9 );
10}
11
12export default SmoothScrolling;
13
Line 1: We have used use client
directive since we want this component to be executed in a browser environment, not on the server.
Line 4: The SmoothScrolling
component takes children
as props. This design allows any child components wrapped in SmoothScrolling
to inherit the smooth scrolling behavior.
Line 6: Here, we return the ReactLenis
component with customized options.
- The
root
attribute indicates thatReactLenis
should control the scrolling of the entire page. So Lenis will be instantiated using<html>
scroll. - The
options
prop configures the behavior of the smooth scrolling effect:lerp: 0.1
: This sets the linear interpolation value for the scrolling effect. A value of 0.1 provides a smooth scroll. The lower the value, the smoother the scrolling.duration: 1.5
: This is the duration (in seconds) of the scrolling effect. A longer duration results in a slower scroll.smoothTouch: true
: This enables smooth scrolling on touch devices. You can try different options from it’s documentation.
Now let’s import this SmoothScrolling.jsx
component in the layout.js
file and wrap around the {children}
as given in the following code snippet.
1import { Inter } from "next/font/google";
2import "./globals.css";
3import SmoothScrolling from "@/components/SmoothScrolling";
4
5const inter = Inter({ subsets: ["latin"] });
6
7export default function RootLayout({ children }) {
8 return (
9 <html lang="en">
10 <body className={inter.className}>
11 <SmoothScrolling>{children}</SmoothScrolling>
12 </body>
13 </html>
14 );
15}
16
Once the SmoothScrolling
component is properly integrated, your Next.js application will have an enhanced scrolling experience. Now to tryout this scroll effect let’s create one ImageList.jsx
component to render multiple images.
In the components folder create one file called ImageList.jsx
and let’s add Images with the help of picsum.photos
1import React from "react";
2import Image from "next/image";
3
4const ImageList = () => {
5 return (
6 <>
7 <Image
8 src={"https://picsum.photos/600/400?random=1"}
9 alt="Image"
10 width={600}
11 height={400}
12 priority
13 sizes="50vw"
14 />
15
16 <Image
17 src={"https://picsum.photos/600/400?random=2"}
18 alt="Image"
19 width={600}
20 height={400}
21 priority
22 sizes="50vw"
23 />
24
25 <Image
26 src={"https://picsum.photos/400/600?random=3"}
27 alt="Image"
28 width={400}
29 height={600}
30 sizes="50vw"
31 />
32
33 <Image
34 src={"https://picsum.photos/600/400?random=4"}
35 alt="Image"
36 width={600}
37 height={400}
38 sizes="50vw"
39 />
40
41 <Image
42 src={"https://picsum.photos/600/400?random=5"}
43 alt="Image"
44 width={600}
45 height={400}
46 sizes="50vw"
47 />
48// Add more images if you like
49 </>
50 );
51};
52
53export default ImageList;
54
In the given component we have used URL provided by picsum.photos to render the images, you can change the width and height in the URL to get images with different width and height. Here we have used Image
component of the Next.js
. Now before we render this component we have to add picsum.photos hostname in the next.config.js
file, so open your config file and add the following.
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "picsum.photos",
},
],
},
};
module.exports = nextConfig;
Now let’s render the ImageList.jsx
component in the page.js
file. Make sure to remove everything and add the following code.
1import ImageList from "@/components/ImageList";
2
3export default function Home() {
4 return (
5 <main className="p-16 xl:p-32 flex flex-col w-full items-center justify-center">
6 <ImageList />
7 </main>
8 );
9}
10
That’s it. Now you will be able to experience the smooth scroll.
Adding Parallax Effects with GSAP
Now, let’s add the parallax effects to our project using GSAP (GreenSock Animation Platform). First create Parallax.jsx
component in the components folder. We’ll break down the Parallax.jsx
component to understand how it works, and then look at how it is used within the ImageList.jsx
component. First let’s import everything as given in the following code snippet.
"use client";
import { gsap } from "gsap";
import { useEffect, useRef } from "react";
import { useWindowSize } from "@studio-freight/hamo";
import { ScrollTrigger } from "gsap/ScrollTrigger";
Here again we have used use client
directive to ensure that the component is executed on the client side. we will use scrollTrigger
plugin from the gsap
to accurately calculate/manipulate the position of the images.
To get the window width
we will use useWindowSize
hook. This hook is imported from @studio-freight/hamo
which is a package that has list of hooks that you can use. It is also created by studio-freight Make sure to install it using the following command:
npm i @studio-freight/hamo
Now let’s add the following code:
1export function Parallax({ className, children, speed = 1, id = "parallax" }) {
2 const trigger = useRef();
3 const target = useRef();
4 const timeline = useRef();
5 const { width: windowWidth } = useWindowSize();
6
7 return (
8 <div ref={trigger} className={className}>
9 <div ref={target}>{children}</div>
10 </div>
11 );
12}
13
Line 1: Here, we define the Parallax
functional component with props:
className
for custom CSS styling.children
to render child components or elements.speed
to control the rate of parallax effect.id
to be used inScrollTrigger
Line 2-4: We use useRef
to create references:
trigger
: The DOM element that triggers the animation.target
: The DOM element that the animation is applied to.timeline
: A GSAP timeline for managing the animation sequence.
Line 5: Here we are getting the width of the window as windowWidth
by using the useWindowSize()
hook.
Line 8-9: We have returned trigger element and target element. Here the children
is wrapped in target element which will have the animation effect.
Now let’s add useEffect
in the same component and add the animation.
useEffect(() => {
gsap.registerPlugin(ScrollTrigger);
const y = windowWidth * speed * 0.1;
const setY = gsap.quickSetter(target.current, "y", "px");
timeline.current = gsap.timeline({
scrollTrigger: {
id: id,
trigger: trigger.current,
scrub: true,
start: "top bottom",
end: "bottom top",
onUpdate: (e) => {
setY(e.progress * y);
},
},
});
return () => {
timeline?.current?.kill();
};
}, [id, speed, windowWidth]);
Line 2: This line registers the ScrollTrigger plugin with GSAP, enabling us to use scroll-based animations.
Line 4: Here, we calculate the vertical movement distance (y
) for the parallax effect based on the window width and the specified speed.
Line 5: Here we create a function that will set the y
position of the element. The gsap.quickSetter()
method is a handy way to create a function that will set a specific property on a specific object. In this case, we want to set the y
property of the target element in pixels.
Line 7-18: We create a GSAP timeline and associate it with a ScrollTrigger instance.
- The ScrollTrigger configuration includes:
id
: A unique identifier for the ScrollTrigger instance.trigger
: The element that triggers the start of the scroll animation.scrub
: When set totrue
, it will make the animation smooth and not jumpy when scrolling up and down the pagestart
andend
: Define the start and end points of the scroll animation. For thestart
It means the animation will start when the top of the trigger element reaches the bottom of the viewport and vice-versa for theend
.onUpdate
: A callback function that updates they
position of the target element based on the scroll progress. Line 21: This cleanup function is called when the component unmounts or the dependencies (id
,speed
,windowWidth
) change. It ensures that theScrollTrigger
instance is properly disposed of to prevent memory leaks.
How to use Parallax Effect
Let’s use the Parallax.jsx
component in the ImageList.jsx
to add the parallax effect in images. Open the ImageList.jsx
component and use the Parallax component as shown n the following code snippet:
import React from "react";
import { Parallax } from "@/components/Parallax";
import Image from "next/image";
const ImageList = () => {
return (
<>
<Parallax speed={1} className="self-start">
<Image
src={"https://picsum.photos/600/400?random=1"}
alt="Image"
width={600}
height={400}
priority
sizes="50vw"
/>
</Parallax>
<Parallax speed={-2} className="self-end overflow-hidden">
<Image
src={"https://picsum.photos/600/400?random=2"}
alt="Image"
width={600}
height={400}
priority
sizes="50vw"
/>
</Parallax>
// ...
</>
);
};
export default ImageList;
First make sure to import the Parallax.jsx
component from the components folder and wrap the Image
inside it. You can pass different speed values and see how the images are moving. You can also add "overflow-hidden"
in the class name to see how the height of the image is changing on scroll.
Best Practices and Performance Optimization
Incorporating smooth scrolling and parallax effects can significantly improve your site’s aesthetic and interactivity. However, it’s important to follow best practices to ensure a smooth, performant experience:
-
Performance Considerations: While combining these effects, be mindful of performance. Smooth scrolling and parallax effects can be resource-intensive, especially on lower-end devices. Always test the performance on various devices and optimize accordingly.
-
User Experience: Strive for a balance where the effects enhance rather than distract. Subtlety is key – overdoing it can lead to a confusing or overwhelming user experience.
-
Optimize Images and Assets: Large images or too many animations can slow down your site. Optimize your assets for the web.
-
Limit the Number of Animations: Too many animations can be distracting and affect performance. Use animations judiciously.
-
Responsive Design: Ensure your animations and effects adapt to different screen sizes for a consistent user experience.
-
Accessibility: Consider users who prefer reduced motion. Provide options to disable animations if necessary.
Conclusion and References
In this blog, we’ve explored how to implement smooth scroll and parallax effects in a Next.js application using Lenis and GSAP. These tools offer powerful capabilities to enhance your website, making it more engaging and interactive. Remember, the key to successful implementation is subtlety and performance optimization. I encourage you to experiment with these techniques and see how they can improve your web projects.
To deepen your understanding and explore more about Next.js, Lenis, GSAP, smooth scrolling, and parallax effects, check out the following resources:
- Next.js Documentation
- Lenis Library
- Lenis Library for React
- GSAP (GreenSock Animation Platform)
- Web Performance Optimization Techniques