Scroll Autoplay

PreviousNext

Displaying different content depending on order and scroll position.

Docs
systaliko-uiblock

Preview

Loading preview…
registry/scroll-animations/scroll-autoplay/index.tsx
'use client';
import { cn } from '@/lib/utils';
import {
  motion,
  HTMLMotionProps,
  MotionValue,
  MapInputRange,
  useScroll,
  useTransform,
} from 'motion/react';
import React from 'react';

interface ScrollAutoplayProps extends HTMLMotionProps<'div'> {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  offset?: any[];
}
interface ScrollAutoPlayItemProps extends HTMLMotionProps<'div'> {
  inputRange: MapInputRange;
  outputRange?: unknown[];
}
interface ScrollAutoplayContextValue {
  scrollYProgress: MotionValue<number>;
}
const ScrollAutoplayContext = React.createContext<
  ScrollAutoplayContextValue | undefined
>(undefined);
function useScrollAutoplayContext() {
  const context = React.useContext(ScrollAutoplayContext);
  if (context === undefined) {
    throw new Error(
      'useScrollAutoplayContext must be used within a ScrollAutoplayContextProvider',
    );
  }
  return context;
}

export function ScrollAutoplay({
  offset = ['0% 50%', '100% 50%'],
  className,
  ...props
}: ScrollAutoplayProps) {
  const scrollRef = React.useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: scrollRef,
    offset: offset,
  });

  return (
    <ScrollAutoplayContext.Provider value={{ scrollYProgress }}>
      <motion.div
        ref={scrollRef}
        className={cn('relative min-h-screen', className)}
        {...props}
      />
    </ScrollAutoplayContext.Provider>
  );
}

export function ScrollAutoplayContainer({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      className={cn('sticky top-0 left-0 w-full min-h-fit', className)}
      {...props}
    />
  );
}

export function ScrollAutoplayItem({
  inputRange,
  outputRange = [0, 1],
  className,
  style,
  ...props
}: ScrollAutoPlayItemProps) {
  const { scrollYProgress } = useScrollAutoplayContext();
  const opacity = useTransform(scrollYProgress, inputRange, outputRange);

  return (
    <motion.div
      className={cn('absolute inset-0 size-full', className)}
      style={{
        opacity,
        willChange: 'opacity',
        ...style,
      }}
      {...props}
    />
  );
}

Installation

npx shadcn@latest add @systaliko-ui/scroll-autoplay

Usage

import { ScrollAutoplay } from "@/components/scroll-autoplay"
<ScrollAutoplay />