A website animation featuring a background image moving on scroll in a parallax motion, made with Framer Motion and React, inside a Next.js app.
Inspired by inkfishnyc.com. Pictures by Matthias Leidinger.
Let's start the project by creating a Next.js application. We can do that by running npx create-next-app@latest client inside of a terminal.
npm i framer-motion.npm i lenis.The basics of creating a background image parallax are as follows:
overflow: hiddencomponents/Intro.jsximport React from "react";
import Image from "next/image";
import Background from "../../public/images/2.jpg";
import { useScroll, useTransform, motion } from "framer-motion";
import { useRef } from "react";
export default function Intro() {
const container = useRef();
const { scrollYProgress } = useScroll({
target: container,
offset: ["start start", "end start"],
});
const y = useTransform(scrollYProgress, [0, 1], ["0vh", "150vh"]);
return (
<div className="h-screen overflow-hidden">
<motion.div style={{ y }} className="relative h-full">
<Image
src={Background}
fill
alt="image"
style={{ objectFit: "cover" }}
/>
</motion.div>
</div>
);
}Couple notes about the code above:
- The progress of the scroll is tracked using the
useScrollHook.- Since the component is the first inside the page, we use an offset of
['start start'].- The progress of the scroll (a value between 0 and 1) is transformed into a value between
0vhand150vhand used as atranslateYvalue.
Then, we add that component inside page.js:
page.js"use client";
import { useEffect } from "react";
import Lenis from "lenis";
import Intro from "@/components/Intro";
import Description from "@/components/Description";
export default function Home() {
useEffect(() => {
const lenis = new Lenis();
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
}, []);
return (
<main>
<Intro />
<Description />
<div className="h-screen"></div>
</main>
);
}The other way of making a background parallax uses the same principles, but instead, the container will be in a fixed position.
components/Section.jsximport Image from "next/image";
import Background from "../../public/images/1.jpg";
import Text from "./components/Text";
import { useScroll, useTransform, motion } from "framer-motion";
import { useRef } from "react";
export default function Section() {
const container = useRef();
const { scrollYProgress } = useScroll({
target: container,
offset: ["start end", "end start"],
});
const y = useTransform(scrollYProgress, [0, 1], ["-10vh", "10vh"]);
return (
<div
ref={container}
className="relative flex h-screen items-center justify-center overflow-hidden"
style={{ clipPath: "polygon(0% 0, 100% 0%, 100% 100%, 0 100%)" }}
>
<Text />
<div className="fixed top-[-10vh] left-0 h-[120vh] w-full">
<motion.div style={{ y }} className="relative h-full w-full">
<Image
src={Background}
fill
alt="image"
style={{ objectFit: "cover" }}
/>
</motion.div>
</div>
</div>
);
}Couple notes about the above code:
- The image container is in a
fixedposition, so we need to add aclip-pathon the parent to crop that fixed div.- The same principles apply from the previous implementation: we translate the image based on the position of the scroll.
- The amount translated here (
-10vhand10vh) should be accounted for in the height of the fixed container (120vh).
That's it for this animation!
A super clean animation that, in my opinion, should be in almost every single website. It really adds a nice dimensionality and brings it to another level of quality. Hope you learned something!