Image Trail
godui/image-trailFreeComponent
Images spawn along the pointer's path, pop in, and drift away — the signature portfolio hover effect.
Install it
npx shadcn@latest add https://godui.design/r/image-trail.jsonSource
1"use client";23import * as React from "react";45export type ImageTrailProps = React.HTMLAttributes<HTMLDivElement> & {6 /** Images cycled through as the pointer moves. */7 images: string[];8 /** Minimum pointer distance (px) between two spawned images. */9 threshold?: number;10 /** Lifespan of each trail image, in ms. */11 duration?: number;12 /** Maximum images alive at once (the recycled DOM pool size). */13 max?: number;14 /** Width/height of each trail image, in px. */15 size?: number;16};1718const ImageTrail = React.forwardRef<HTMLDivElement, ImageTrailProps>(19 (20 {21 images,22 threshold = 64,23 duration = 750,24 max = 12,25 size = 180,26 className,27 children,28 onPointerMove,29 ...props30 },31 forwardedRef,32 ) => {33 const ref = React.useRef<HTMLDivElement>(null);34 React.useImperativeHandle(35 forwardedRef,36 () => ref.current as HTMLDivElement,37 );3839 // Pool of <img> slots, recycled round-robin. We drive everything with the40 // Web Animations API and direct style writes — no React re-render per move.41 const slots = React.useRef<(HTMLImageElement | null)[]>([]);42 const last = React.useRef<{ x: number; y: number } | null>(null);43 const slotIndex = React.useRef(0);44 const imageIndex = React.useRef(0);4546 const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {47 onPointerMove?.(e);48 const el = ref.current;49 if (!el || images.length === 0) return;5051 const rect = el.getBoundingClientRect();52 const x = e.clientX - rect.left;53 const y = e.clientY - rect.top;5455 const prev = last.current;56 if (prev) {57 const dx = x - prev.x;58 const dy = y - prev.y;59 if (Math.hypot(dx, dy) < threshold) return;60 // Tilt the image a touch toward the direction of travel.61 spawn(x, y, Math.atan2(dy, dx));62 } else {63 spawn(x, y, 0);64 }65 last.current = { x, y };66 };6768 const spawn = (x: number, y: number, angleRad: number) => {69 const img = slots.current[slotIndex.current % max];70 slotIndex.current += 1;71 if (!img) return;7273 img.src = images[imageIndex.current % images.length] as string;74 imageIndex.current += 1;7576 const tilt = Math.max(-12, Math.min(12, (angleRad * 180) / Math.PI / 6));77 img.style.left = `${x}px`;78 img.style.top = `${y}px`;7980 img.animate(81 [82 {83 opacity: 0,84// … truncated
What it pulls in
Other registry items
Files it writes
More from godui
All 105 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | godui | Components | Free | 1 dep | |
| Agent Flowagent-flow | godui | Components | Free | 1 dep | |
| Agent Timelineagent-timeline | godui | Components | Free | 1 dep | |
| Animated Beamanimated-beam | godui | Components | Free | 1 dep | |
| Animated Testimonialsanimated-testimonials | godui | Components | Free | 1 dep | |
| Animated Tooltipanimated-tooltip | godui | Components | Free | 1 dep | |
| App Showcaseapp-showcase | godui | Components | Free | 1 dep | |
| Aurora Textaurora-text | godui | Components | Free | no deps |
