Logo

Kaif UI

logoKaif UI
Components

Infinite Slider

Infinite Slider

Keep users engaged with endless content loading using the Infinite Slider component.

Infinite Slider Vertical

Image 1
Image 2
Image 3
Image 4
Image 5
Image 1 second row
Image 2 second row
Image 3 second row
Image 4 second row
Image 5 second row
Image 1
Image 2
Image 3
Image 4
Image 5
Image 1 second row
Image 2 second row
Image 3 second row
Image 4 second row
Image 5 second row
Image 1
Image 2
Image 3
Image 4
Image 5
Image 1 second row
Image 2 second row
Image 3 second row
Image 4 second row
Image 5 second row
Image 1
Image 2
Image 3
Image 4
Image 5
Image 1 second row
Image 2 second row
Image 3 second row
Image 4 second row
Image 5 second row

Infinite Slider Horizontal

Image 1
Image 2
Image 3
Image 4
Image 5
Image 1 second row
Image 2 second row
Image 3 second row
Image 4 second row
Image 5 second row
Image 1
Image 2
Image 3
Image 4
Image 5
Image 1 second row
Image 2 second row
Image 3 second row
Image 4 second row
Image 5 second row
Image 1
Image 2
Image 3
Image 4
Image 5
Image 1 second row
Image 2 second row
Image 3 second row
Image 4 second row
Image 5 second row
Image 1
Image 2
Image 3
Image 4
Image 5
Image 1 second row
Image 2 second row
Image 3 second row
Image 4 second row
Image 5 second row

Installation

Install Dependencies

npm i framer-motion react-use-measure

Add util file

lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Copy the source code

components/kaif-ui/infiniteSlider.tsx
'use client';
 
// Visit https://kaif-ui.vercel.app/ for more components like this
 
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>
  );
}

Tech Stack

Props

PropTypeDefault
children
React.ReactNode
None
gap
number
16
duration
number
25
durationOnHover
number
None
direction
"horizontal" | "vertical"
horizontal
reverse
boolean
-
className
string
None

On this page

Brought to you by Muhammad Kaif Nazeer