December 6, 2023

How to implement smooth scrolling in Next.js with Lenis and GSAP

12 min read
0 views
cover image for a blog on 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.

SmoothScrolling.jsx
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 that ReactLenis 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.

SmoothScrolling.jsx
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

ImageList.jsx
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.

next.config.js
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.

page.js
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.

Parallax.jsx
"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:

Parallax.jsx
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 in ScrollTrigger

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 to true, it will make the animation smooth and not jumpy when scrolling up and down the page
    • start and end: Define the start and end points of the scroll animation. For the start It means the animation will start when the top of the trigger element reaches the bottom of the viewport and vice-versa for the end.
    • onUpdate: A callback function that updates the y 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 the ScrollTrigger 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:

ImageList.jsx
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:

Thanks For Reading😄

Like this article? Share this on👇