Infinite Slider

Infinite scrolling slider component that smoothly loops through its children. It supports both horizontal and vertical directions, with customizable speed and speed on hover. Ideal for creating continuous carousels, marquee displays, or dynamic content showcases.

Example

Infinite Slider basic

Apple Music logoChrome logoStrava logoNintendo logoJquery logoPrada logoApple Music logoChrome logoStrava logoNintendo logoJquery logoPrada logo

Infinite Slider hover speed

Dean blunt - Black Metal 2Jungle Jack - JUNGLE DES ILLUSIONS VOL 2Yung Lean - StardustLana Del Rey - UltraviolenceA$AP Rocky - Tailor SwifMidnight Miami (feat Konvy) - Nino Paid, KonvyDean blunt - Black Metal 2Jungle Jack - JUNGLE DES ILLUSIONS VOL 2Yung Lean - StardustLana Del Rey - UltraviolenceA$AP Rocky - Tailor SwifMidnight Miami (feat Konvy) - Nino Paid, Konvy

Infinite Slider vertical

Dean blunt - Black Metal 2Jungle Jack - JUNGLE DES ILLUSIONS VOL 2Yung Lean - StardustLana Del Rey - UltraviolenceA$AP Rocky - Tailor SwifMidnight Miami (feat Konvy) - Nino Paid, KonvyDean blunt - Black Metal 2Jungle Jack - JUNGLE DES ILLUSIONS VOL 2Yung Lean - StardustLana Del Rey - UltraviolenceA$AP Rocky - Tailor SwifMidnight Miami (feat Konvy) - Nino Paid, Konvy
DAYS BEFORE RODEO - Travis ScottYou're in My System - TORYONTHEBEATYou can't tell me - People Make the World Go Roundye - Kanye WestSlime Season 3 - Young ThugSWAG - 8rukiDAYS BEFORE RODEO - Travis ScottYou're in My System - TORYONTHEBEATYou can't tell me - People Make the World Go Roundye - Kanye WestSlime Season 3 - Young ThugSWAG - 8ruki

Code

'use client';
import { cn } from '@/lib/utils';
import { useMotionValue, animate, motion } from 'framer-motion';
import { useState, useEffect } from 'react';
import useMeasure from 'react-use-measure';

type InfiniteSliderProps = {
  children: React.ReactNode;
  gap?: number;
  duration?: number;
  durationOnHover?: number;
  direction?: 'horizontal' | 'vertical';
  reverse?: boolean;
  className?: string;
};

export function InfiniteSlider({
  children,
  gap = 16,
  duration = 25,
  durationOnHover,
  direction = 'horizontal',
  reverse = false,
  className,
}: InfiniteSliderProps) {
  const [currentDuration, setCurrentDuration] = useState(duration);
  const [ref, { width, height }] = useMeasure();
  const translation = useMotionValue(0);
  const [isTransitioning, setIsTransitioning] = useState(false);
  const [key, setKey] = useState(0);

  useEffect(() => {
    let controls;
    const size = direction === 'horizontal' ? width : height;
    const contentSize = size + gap;
    const from = reverse ? -contentSize / 2 : 0;
    const to = reverse ? 0 : -contentSize / 2;

    if (isTransitioning) {
      controls = animate(translation, [translation.get(), to], {
        ease: 'linear',
        duration:
          currentDuration * Math.abs((translation.get() - to) / contentSize),
        onComplete: () => {
          setIsTransitioning(false);
          setKey((prevKey) => prevKey + 1);
        },
      });
    } else {
      controls = animate(translation, [from, to], {
        ease: 'linear',
        duration: currentDuration,
        repeat: Infinity,
        repeatType: 'loop',
        repeatDelay: 0,
        onRepeat: () => {
          translation.set(from);
        },
      });
    }

    return controls?.stop;
  }, [
    key,
    translation,
    currentDuration,
    width,
    height,
    gap,
    isTransitioning,
    direction,
    reverse,
  ]);

  const hoverProps = durationOnHover
    ? {
        onHoverStart: () => {
          console.log('hello');

          setIsTransitioning(true);
          setCurrentDuration(durationOnHover);
        },
        onHoverEnd: () => {
          setIsTransitioning(true);
          setCurrentDuration(duration);
        },
      }
    : {};

  return (
    <div className={cn('overflow-hidden', className)}>
      <motion.div
        className='flex w-max'
        style={{
          ...(direction === 'horizontal'
            ? { x: translation }
            : { y: translation }),
          gap: `${gap}px`,
          flexDirection: direction === 'horizontal' ? 'row' : 'column',
        }}
        ref={ref}
        {...hoverProps}
      >
        {children}
        {children}
      </motion.div>
    </div>
  );
}

Component API

InfiniteSlider

PropTypeDefaultDescription
childrenReact.ReactNodeThe child elements to be displayed in the slider.
gapnumber16The gap between child elements in pixels.
durationnumber25The duration of a full cycle of the slider animation in seconds.
durationOnHovernumberThe duration of the animation when hovered, in seconds.
direction'horizontal' | 'vertical''horizontal'The direction of the slider movement.
reversebooleanfalseWhether to reverse the direction of the slider movement.
classNamestringundefinedOptional CSS class for styling the component.