Infinite Marquee

A component for displaying a continuous scrolling content.

Infinite Marquee
Live controls for speed, hover pause/scale, and logo size; reset remounts the track so the animation restarts
React
JavaScript
TypeScript
Node.js
HTML5
CSS3
React
JavaScript
TypeScript
Node.js
HTML5
CSS3
React
JavaScript
TypeScript
Node.js
HTML5
CSS3
React
JavaScript
TypeScript
Node.js
HTML5
CSS3
1"use client";
2
3import React from "react";
4import Image from "next/image";
5import { cva, type VariantProps } from "class-variance-authority";
6
7import { cn } from "@/lib/utils";
8
9const marqueeRootVariants = cva("relative flex w-full overflow-x-hidden", {
10 variants: {
11 pauseOnHover: {
12 true: "infinite-marquee-root",
13 false: null,
14 },
15 },
16 defaultVariants: {
17 pauseOnHover: true,
18 },
19});
20
21const marqueeTrackVariants = cva(
22 "flex min-w-full shrink-0 items-center justify-around gap-4 py-5",
23 {
24 variants: {
25 speed: {
26 slow: "animate-marquee-slow",
27 normal: "animate-marquee",
28 fast: "animate-marquee-fast",
29 },
30 pauseOnHover: {
31 true: "infinite-marquee-track",
32 false: null,
33 },
34 },
35 defaultVariants: {
36 speed: "normal",
37 pauseOnHover: true,
38 },
39 }
40);
41
42const marqueeImageVariants = cva("h-auto w-auto", {
43 variants: {
44 scaleOnHover: {
45 true: "transition-all duration-300 hover:scale-110",
46 false: null,
47 },
48 },
49 defaultVariants: {
50 scaleOnHover: true,
51 },
52});
53
54interface InfiniteMarqueeProps
55 extends VariantProps<typeof marqueeRootVariants>,
56 VariantProps<typeof marqueeTrackVariants>,
57 VariantProps<typeof marqueeImageVariants> {
58 logos?: Logo[];
59 imageWidth?: number;
60 imageHeight?: number;
61 className?: string;
62}
63
64export interface Logo {
65 src: string;
66 alt: string;
67}
68
69const defaultLogos: Logo[] = [
70 {
71 src: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/react/react-original.svg",
72 alt: "React",
73 },
74 {
75 src: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/javascript/javascript-original.svg",
76 alt: "JavaScript",
77 },
78 {
79 src: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/typescript/typescript-original.svg",
80 alt: "TypeScript",
81 },
82 {
83 src: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/nodejs/nodejs-original.svg",
84 alt: "Node.js",
85 },
86 {
87 src: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/html5/html5-original.svg",
88 alt: "HTML5",
89 },
90 {
91 src: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/css3/css3-original.svg",
92 alt: "CSS3",
93 },
94];
95
96export default function InfiniteMarquee({
97 logos = defaultLogos,
98 pauseOnHover = true,
99 scaleOnHover = true,
100 speed = "normal",
101 imageWidth = 100,
102 imageHeight = 100,
103 className,
104}: InfiniteMarqueeProps) {
105 const isExplicitlyFalse = (v: unknown) =>
106 v === false ||
107 v === 0 ||
108 (typeof v === "string" && v.toLowerCase() === "false");
109
110 const pause = !isExplicitlyFalse(pauseOnHover);
111 const scale = !isExplicitlyFalse(scaleOnHover);
112 const speedSafe =
113 speed === "slow" || speed === "normal" || speed === "fast"
114 ? speed
115 : "normal";
116 const w = Math.round(
117 Number.isFinite(Number(imageWidth)) ? Number(imageWidth) : 100,
118 );
119 const h = Math.round(
120 Number.isFinite(Number(imageHeight)) ? Number(imageHeight) : 100,
121 );
122
123 const marqueeContent = (
124 <>
125 {logos.map((logo, index) => (
126 <div
127 key={index}
128 className="flex items-center justify-center"
129 style={{ width: `${w}px` }}
130 >
131 <Image
132 src={logo.src}
133 alt={logo.alt}
134 width={w}
135 height={h}
136 className={marqueeImageVariants({ scaleOnHover: scale })}
137 />
138 </div>
139 ))}
140 </>
141 );
142
143 return (
144 <div className={cn(marqueeRootVariants({ pauseOnHover: pause }), className)}>
145 <div className={marqueeTrackVariants({ speed: speedSafe, pauseOnHover: pause })}>
146 {marqueeContent}
147 {marqueeContent}
148 </div>
149 <div className={marqueeTrackVariants({ speed: speedSafe, pauseOnHover: pause })}>
150 {marqueeContent}
151 {marqueeContent}
152 </div>
153 </div>
154 );
155}

Props

NameTypeDefaultDescription
logosLogo[]defaultLogosArray of logo objects to display in the marquee
pauseOnHoverbooleantrueWhether to pause the animation on hover
speed'slow' | 'normal' | 'fast''normal'Animation speed of the marquee
scaleOnHoverbooleantrueWhether to scale logos on hover
imageWidthnumber100Width of the logo images in pixels
imageHeightnumber100Height of the logo images in pixels