by Shon Danvudi.
4/6/2025
Creating a Seamless Marquee in React with Framer Motion

This tutorial will guide you through the steps to implement a seamless looping marquee effect in React using Framer Motion (Motion Dev).
1. Building an Infinite Marquee
Building an Infinite Marquee
Framer Motion (now Motion Dev) does lack examples on the web. The library itself has been out for quite some time, but has undergone many updates to the website documentation. This often gets developers stuck and leads to the replacement of the framework with other libraries, which also often creates other complications.This tutorial will guide you through the steps to implement a seamless looping marquee effect for your React application. I am going to be using Next.js 15 and app router, but the implementation should work with any React framework.
//MarqueeItem.tsx
How this works is pretty simple. Assuming we have an array of strings that we would like to loop horizontally and infinitely. Depending on the number of items in the array, we need to manually adjust the duration value. Even though there are two
<motion.div>
this is a single marquee. Once the first div ends, the second one follows it right after to create an infinite marquee illusion. Simple, flawless, infinite. import { motion } from "framer-motion";
import Image from "next/image";
export default function MarqueeItem({images, from, to}:{ images:string[],
from:number | string, to:number | string }){
return (
<div className="flex my-24">
<motion.div
initial={{ x: `${from}` }}
animate={{ x: `${to}` }}
transition={{ duration: 60, repeat: Infinity, ease: "linear" }}
className="flex flex-shrink-0"
>
{images.map((image:any, index:number) => {
return <Image width={200} height={200} alt={`marqueImg${index}`}
className="object-contain pr-20" src={image} key={index} />
})}
</motion.div>
<motion.div
initial={{ x: `${from}` }}
animate={{ x: `${to}` }}
transition={{ duration: 60, repeat: Infinity, ease: "linear" }}
className="flex flex-shrink-0"
>
{images.map((image:any, index:number) => {
return <Image width={200} height={200} alt={`marqueImg${index}`}
className="object-contain pr-20" src={image} key={index} />;
})}
</motion.div>
</div>
);
};
Now, to call this component, I created another component called Marquee.tsx. This isn’t necessary because we can call the MarqueeItem.tsx anywhere in the app as long as it contains “use client” since
{motion}
can only run on the client. import MarqueeItem from "./MarqueeItem";
export default function Marquee() {
const images = [
"/imageOne.webp",
"/imageTwo.webp",
"/imageThree.webp",
"/imageFour.webp",
"/imageFive.webp",
"/imageSix.webp",
"/imageSeven.webp"
];
return (
<div className="container mx-auto overflow-hidden select-none">
<MarqueeItem images={images} from={0} to={"-100%"} />
</div>
);
};