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},[])
11Our 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}
13Create 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";
6Framer-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%"]);
8scrollYProgress 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);
13300vh to have lots of room for scrollingtargetRef to the parent sectionmotion.div