Implement Smooth Scrolling & Parallax Effect in Next.js using Lenis and GSAP
Tags: nextjs, webdev, tutorial, react
Published: 2023-12-06 10:35:48

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](https://smooth-scroll-next-js.vercel.app/)

If you prefer to watch the video here is the video tutorial:

{% embed https://youtu.be/QNh0MH-G3OM %}  

## What is Smooth Scrolling and Parallax Effects?

Let's simplify 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: 

```bash
√ 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. 

```bash
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](https://github.com/studio-freight/react-lenis). 

```react
"use client";
import { ReactLenis } from "@studio-freight/react-lenis";

function SmoothScrolling({ children }) {
  return (
    <ReactLenis root options={{ lerp: 0.1, duration: 1.5, smoothTouch: true }}>
      {children}
    </ReactLenis>
  );
}

export default SmoothScrolling;
```

**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](https://github.com/studio-freight/lenis#instance-settings). 

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. 

```react
import { Inter } from "next/font/google";
import "./globals.css";
import SmoothScrolling from "@/components/SmoothScrolling";

const inter = Inter({ subsets: ["latin"] });

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <SmoothScrolling>{children}</SmoothScrolling>
      </body>
    </html>
  );
}
```

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](https://picsum.photos/)

```react
import React from "react";
import Image from "next/image";

const ImageList = () => {
  return (
    <>
      <Image
        src={"https://picsum.photos/600/400?random=1"}
        alt="Image"
        width={600}
        height={400}
        priority
        sizes="50vw"
      />

      <Image
        src={"https://picsum.photos/600/400?random=2"}
        alt="Image"
        width={600}
        height={400}
        priority
        sizes="50vw"
      />

      <Image
        src={"https://picsum.photos/400/600?random=3"}
        alt="Image"
        width={400}
        height={600}
        sizes="50vw"
      />

      <Image
        src={"https://picsum.photos/600/400?random=4"}
        alt="Image"
        width={600}
        height={400}
        sizes="50vw"
      />

      <Image
        src={"https://picsum.photos/600/400?random=5"}
        alt="Image"
        width={600}
        height={400}
        sizes="50vw"
      />
// Add more images if you like
    </>
  );
};

export default ImageList;

```

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. 

```react
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. 

```react
import ImageList from "@/components/ImageList";

export default function Home() {
  return (
    <main className="p-16 xl:p-32 flex flex-col w-full items-center justify-center">
      <ImageList />
    </main>
  );
}

```

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. 

```react
"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](https://github.com/studio-freight/hamo) Make sure to install it using the following command:

```bash
npm i @studio-freight/hamo
```

Now let's add the following code:

```react
export function Parallax({ className, children, speed = 1, id = "parallax" }) {
  const trigger = useRef();
  const target = useRef();  
  const timeline = useRef(); 
  const { width: windowWidth } = useWindowSize();

  return (
    <div ref={trigger} className={className}>
      <div ref={target}>{children}</div>
    </div>
  );
}
```

**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. 

```react
  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:

```react
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](https://nextjs.org/docs)
- [Lenis Library](https://github.com/studio-freight/lenis)
- [Lenis Library for React](https://github.com/studio-freight/react-lenis)
- [GSAP (GreenSock Animation Platform)](https://gsap.com/)
- [Web Performance Optimization Techniques](https://web.dev/learn/performance)

## Thanks For Reading😄

Checkout my other articles: 

- [Build a custom pagination component in ReactJS from scratch]
(https://devdreaming.com/blogs/create-pagination-component-reactjs)

- [Form validation in ReactJS: build a reusable custom hook for inputs and error handling]
(https://devdreaming.com/blogs/react-form-validation-custom-hook)

Feel free to visit my youtube channel: 
[@CodeBucks](https://www.youtube.com/channel/UCeYt6blRBKuNrEg_-282fSA)

Checkout my personal blog website:
[DevDreaming](https://devdreaming.com)

Follow me on Twitter where I share lots of useful resources!
[@code_bucks](https://twitter.com/code_bucks) 😉