Tutorials
Malik Kotb
Feb 5, 2024 / Beginner / Short
A website tutorial featuring a horizontal scroll section using Framer Motion and Next.js.
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.
npm i framer-motion
.npm i @studio-freigt/lenis
.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
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
Create a components directory (next to the app directory) and create a new file HorizontalScroll.tsx
.
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
.
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
scrollYProgress
will return the progress of scrolling vertically on the section.useTransform
takes in a motionValue, an array of values to map from, and array of values to map toWe 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
300vh
to have lots of room for scrollingtargetRef
to the parent sectionmotion.div