UNPKG

react-dvd-screensaver

Version:

DVD-era nostalgia in React. Zero dependencies, fully typed, SSR-safe, supports React 16 through 19.

115 lines (113 loc) 4.15 kB
/** * Configuration options for the DVD screensaver behavior. */ type Options = { /** Whether the animation should pause when the element is hovered or touched. */ freezeOnHover?: boolean; /** Callback triggered each time the element hits a container boundary. Receives the total impact count. */ impactCallback?: (count: number) => void; /** Callback triggered when the element simultaneously hits two boundaries (a corner). */ onCornerHit?: () => void; /** Callback triggered when hover or touch begins on the element. */ hoverCallback?: () => void; /** Pause the animation programmatically. */ paused?: boolean; /** * Speed of the animation expressed as pixels per frame at 60 fps. Defaults to 2. * Internally the animation is time-based so the speed stays consistent across * 60 Hz, 90 Hz, and 120 Hz displays. */ speed?: number; }; /** * The return type of the useDvdScreensaver hook. */ type UseDvdScreensaver<T extends HTMLElement = HTMLDivElement> = { /** Attach to the container element. Takes precedence over the element's parent when provided. */ containerRef: React.RefObject<T | null>; /** Attach to the element that should animate. */ elementRef: React.RefObject<T | null>; /** Whether the element is currently hovered or touched. */ hovered: boolean; /** Total number of boundary impacts. */ impactCount: number; }; /** * Hook that applies a DVD screensaver bouncing animation to an element. * Works on both desktop (mouse) and mobile (touch) devices, and is safe * to use in Next.js client components. * * The container must have `position: relative` (or `absolute` / `fixed`). * The animated element must have `position: absolute; top: 0; left: 0`. * If `containerRef` is not attached to a DOM node, the element's `parentElement` is used. * * @param options - Configuration options. * @returns Refs for the container and element, hover state, and impact count. * * @example Basic usage * ```tsx * const { containerRef, elementRef, impactCount } = useDvdScreensaver({ speed: 3 }); * * return ( * <div ref={containerRef} style={{ position: 'relative', width: '100%', height: '400px' }}> * <div ref={elementRef} style={{ position: 'absolute', top: 0, left: 0 }}> * <MyLogo /> * </div> * </div> * ); * ``` * * @example Change color on every boundary impact * ```tsx * const COLORS = ['#ff4081', '#7c4dff', '#00bcd4', '#69f0ae']; * const [color, setColor] = useState(COLORS[0]); * * const { containerRef, elementRef } = useDvdScreensaver({ * impactCallback: (count) => setColor(COLORS[count % COLORS.length]), * }); * ``` * * @example Programmatic pause and resume * ```tsx * const [paused, setPaused] = useState(false); * const { containerRef, elementRef } = useDvdScreensaver({ paused }); * * return ( * <> * <div ref={containerRef} style={{ position: 'relative', width: '100%', height: '400px' }}> * <div ref={elementRef} style={{ position: 'absolute', top: 0, left: 0 }}> * <MyLogo /> * </div> * </div> * <button onClick={() => setPaused((p) => !p)}>{paused ? 'Resume' : 'Pause'}</button> * </> * ); * ``` * * @example Corner hit detection * ```tsx * const { containerRef, elementRef } = useDvdScreensaver({ * onCornerHit: () => console.log('Corner!'), * }); * ``` * * @example Next.js App Router — add 'use client' directive * ```tsx * 'use client'; * import { useDvdScreensaver } from 'react-dvd-screensaver'; * * export default function ScreensaverPage() { * const { containerRef, elementRef } = useDvdScreensaver({ speed: 3 }); * * return ( * <div ref={containerRef} style={{ position: 'relative', width: '100%', height: '100vh' }}> * <div ref={elementRef} style={{ position: 'absolute', top: 0, left: 0 }}> * <MyLogo /> * </div> * </div> * ); * } * ``` */ declare function useDvdScreensaver<T extends HTMLElement = HTMLDivElement>(options?: Partial<Options>): UseDvdScreensaver<T>; export { type Options, type UseDvdScreensaver, useDvdScreensaver };