Tutorials

avatar

Malik Kotb

Feb 5, 2024 / Beginner / Short

Horizontal Section
Horizontal Section with Framer Motion and Next.js

A website tutorial featuring a horizontal scroll section using Framer Motion and Next.js.

Initializing the project

Let's start the project by setting up a Next.js app with TypeScript and utilizing the app router. Just run npx create-next-app@latest in the terminal.

We can clear out all the contents in the page.tsx, and global.css files, and add our own HTML and CSS, to begin with a clean slate in the application.

Adding Smooth Scroll

This animation is made by first adding a smooth scroll. It's not necessary for the animation, but adds smoothness.

For that, we can use Lenis Scroll:

page.tsx

1useEffect( () => {
2    const lenis = new Lenis()
3   
4    function raf(time) {
5        lenis.raf(time)
6        requestAnimationFrame(raf)
7    }
8
9    requestAnimationFrame(raf)
10},[])
11

Page Component

Our page component is a flex container where we can import our three main sections. We can style the top and bottom sections to our choosing.

page.tsx

1import HorizontalScroll from  "./components/HorizontalScroll";
2import TopSection from  "./components/TopSection";
3import BottomSection from  "./components/BottomSection";
4export  default  function  Home()  {
5return (
6	<main className="flex flex-col mb-12 w-full ">
7		<TopSection  />
8		<HorizontalScroll  />
9		<BottomSection  />
10	</main>
11  );
12}
13

HorizontalScroll Component

Create a components directory (next to the app directory) and create a new file HorizontalScroll.tsx.

Imports

page.tsx

1"use client";
2
3import  { motion,  useScroll,  useTransform }  from  "framer-motion";
4
5import  { useRef }  from  "react";
6

Framer-Motion only works in client components (and App router by default uses server components), therefore we add "use client" at the top of the page. We need to import motion, useScroll, and useTransform from framer-motion.

Essentials

Here are the essentials of making this animation. We want to have a main container with a long scroll, something like 300vh and inside of it have a sticky top-0 container of 100vh that will stick throughout the whole length of its parent.

Then we can track the progress of the scroll and transform our div accordingly.

page.tsx

1const targetRef = useRef(null);
2const { scrollYProgress } = useScroll({
3	target: targetRef,	
4	offset: ["start start",  "end end"],
5});
6
7const x = useTransform(scrollYProgress, [0, 1], ["0%", "-70%"]);
8

JSX

We can follow our viewport as we scroll further down, by setting the position of the parent div to sticky and top-0 => when we scroll, the child div stays in view until we reach the bottom of the parent component.

page.tsx

1return  (
2<section ref={targetRef} className="h-[300vh]">
3	<div className="h-[80vh] pt-[10vh] sticky top-0 flex items-center 	overflow-hidden">
4		<motion.div  style={{ x: x }}  className="flex gap-24">
5			<Image src={"image"} alt="description" fill />
6			<Image src={"image"} alt="description" fill />
7			<Image src={"image"} alt="description" fill />
8			<Image src={"image"} alt="description" fill />
9		</motion.div>
10	</div>
11</section>
12);
13
We should have something like this:
Wrapping up.

Quick and smooth, right?

Hope you liked the animation and learned something new!

-Malik

Things for creative devs
sent to your inbox every week