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
Infinite Slider hover speed
Infinite Slider vertical
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: () => {
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>
);
}
Please add:
Component API
InfiniteSlider
Prop | Type | Default | Description |
---|---|---|---|
children | React.ReactNode | The child elements to be displayed in the slider. | |
gap | number | 16 | The gap between child elements in pixels. |
duration | number | 25 | The duration of a full cycle of the slider animation in seconds. |
durationOnHover | number | The duration of the animation when hovered, in seconds. | |
direction | 'horizontal' | 'vertical' | 'horizontal' | The direction of the slider movement. |
reverse | boolean | false | Whether to reverse the direction of the slider movement. |
className | string | undefined | Optional CSS class for styling the component. |