Image Trail
Images dropped along the pointer's path, spaced by distance travelled rather than by time.
Move the pointer across this panel.
Installation
npx shadcn@latest add @loomui/image-trailUsage
import { ImageTrail } from "@/components/ui/image-trail"<ImageTrail images={["/work/1.jpg", "/work/2.jpg", "/work/3.jpg"]}>
<h1>Selected work</h1>
</ImageTrail>Images are dropped by distance travelled, not on a timer. A slow drag across the panel leaves the same trail a fast flick does, and a pointer sitting still leaves nothing at all. On a timer, a paused pointer piles the whole set on one spot, which is the failure mode this effect is usually shipped with.
Anything you pass as children sits above the trail, so the effect goes behind a heading rather than in place of one. The panel is the hit area, so give it the size you want the effect to have.
Spacing and life
<ImageTrail images={IMAGES} distance={140} life={700} max={8} />distance is how far the pointer must travel before the next image is dropped.
Lower it and the trail becomes a dense ribbon. Raise it and you get a handful
of deliberate marks. Around 90 is a trail. Around 200 is a breadcrumb.
life is how long one image takes to arrive, hold and go. It lands quickly and
leaves slowly: an image that fades in at the same rate it fades out is never
legible at any point in its life.
max caps how many are alive at once. Older images are dropped first, which
keeps a fast sweep across a wide panel from putting eighty layers on screen.
Each image is removed on animationend rather than on a timer, so a tab that
is backgrounded mid-sweep does not come back with a trail frozen across it.
Sizing the images
<ImageTrail images={IMAGES} imageClassName="h-40 w-32 rounded-2xl" />className sizes the panel, imageClassName sizes what lands in it. Images
are object-cover, so a mixed set of portrait and landscape sources still
lands as one consistent shape.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
images | string[] | none | Sources dropped in order, on a loop. |
distance | number | 90 | Pixels of travel between drops. |
life | number | 900 | Milliseconds an image takes to arrive, hold and go. |
max | number | 12 | Most images alive at once. |
tilt | number | 14 | Largest tilt either side of straight, in degrees. |
imageClassName | string | none | Merged onto each image. Size and radius here. |
className | string | none | Merged onto the panel. |
Any other <div> prop is forwarded.
Accessibility
Dropped images are decoration. They are aria-hidden, carry an empty alt,
and are neither focusable nor draggable. The panel's real content is all a
screen reader or a keyboard ever meets. Under prefers-reduced-motion nothing
is dropped at all, and the panel is just a panel.