KQ-886 · GIF/LOOP
GIF Loop
A looping picture that plays only where it can be seen. Each clip is drawn rather than fetched — a seeded hash turns the clip's seed and frame index into a skyline, ground stripes and a marker arcing across the frame — and an IntersectionObserver rooted on the thread's own scroll box stops the tick when a clip falls below the visible threshold, landing a play badge on snap behind a fading scrim while a rail head travels one loop's width on a linear tween. Space or Enter pauses a clip by hand, aria-pressed carries that state, and under reduced motion nothing plays itself: the button becomes a frame step, one press per frame.
- Bay 3 door cycle
- Yard gate, 07:12
- Belt run, north line
Bay 3 door cycle · playing · frame 01 · 0 loops
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/gif-loop
Props
- clips
- GifClip[]
- default —
- The thread, oldest first: id, side, title, seed, frame count, time and delivery.
- paused / defaultPaused
- string[]
- default []
- Controlled or initial ids of clips paused by hand.
- onPausedChange
- (id: string, paused: boolean) => void
- default —
- Fires from the press or key that pauses or resumes a clip by hand.
- onFrameChange
- (id: string, frame: number) => void
- default —
- Fires on each frame a clip advances to.
- onLoop
- (id: string, loops: number) => void
- default —
- Fires as a clip wraps past its last frame, with its running loop count.
- onInViewChange
- (id: string, inView: boolean) => void
- default —
- Fires with the first reading and whenever a clip crosses the threshold.
- fps
- number
- default 8
- Frames a second while a clip plays.
- threshold
- number
- default 0.6
- Visible share a clip must hold in the viewport to keep playing.
- viewportHeight
- number
- default 264
- Height of the thread's scroll viewport, in pixels.
- viewportRef
- React.RefObject<HTMLDivElement | null>
- default —
- Hands the scroll viewport to the host so it can scroll the thread itself.
- peerName
- string
- default "Them"
- Names the other side in delivery and clip sentences.
- label
- string
- default —
- Names the scrollable thread region for assistive technology.
| Prop | Type | Default | Description |
|---|---|---|---|
| clips | GifClip[] | — | The thread, oldest first: id, side, title, seed, frame count, time and delivery. |
| paused / defaultPaused | string[] | [] | Controlled or initial ids of clips paused by hand. |
| onPausedChange | (id: string, paused: boolean) => void | — | Fires from the press or key that pauses or resumes a clip by hand. |
| onFrameChange | (id: string, frame: number) => void | — | Fires on each frame a clip advances to. |
| onLoop | (id: string, loops: number) => void | — | Fires as a clip wraps past its last frame, with its running loop count. |
| onInViewChange | (id: string, inView: boolean) => void | — | Fires with the first reading and whenever a clip crosses the threshold. |
| fps | number | 8 | Frames a second while a clip plays. |
| threshold | number | 0.6 | Visible share a clip must hold in the viewport to keep playing. |
| viewportHeight | number | 264 | Height of the thread's scroll viewport, in pixels. |
| viewportRef | React.RefObject<HTMLDivElement | null> | — | Hands the scroll viewport to the host so it can scroll the thread itself. |
| peerName | string | "Them" | Names the other side in delivery and clip sentences. |
| label | string | — | Names the scrollable thread region for assistive technology. |