# Image sequence (goodtabs UI)

A pinned canvas that scrubs through a numbered image sequence as you scroll.

## Install

```bash
npx shadcn@latest add https://ui.goodtabs.co/r/image-sequence.json
```

Or, with the @goodtabs namespace configured in components.json: `npx shadcn@latest add @goodtabs/image-sequence`

## Usage

```tsx
"use client"

import { ImageSequence } from "@/components/ui/image-sequence"

<ImageSequence
  frames={48}
  src={(i) => `/turntable/${String(i).padStart(2, "0")}.jpg`}
  stillFrame={24}
  alt="The product turning through a full circle."
>
  One full turn.
</ImageSequence>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| frames | `number` |  | How many frames the sequence has, required. |
| src | `(index: number) => string` |  | Turns a zero-based frame index into its URL, required. |
| alt | `string` |  | Describes the sequence, required. Goes on the real image. |
| stillFrame | `number` | `0` | The zero-based frame shown on phones, under reduced motion and with no script. |
| pace | `number` | `32` | Scroll distance per frame, in pixels. |
| children | `ReactNode` |  | The caption under the image. |

## When to use

- A product turntable or an exploded view rendered to frames
- A short film people should control with scroll
- Once on a landing page, as the moment people stop to play with

## When not to use

- For footage with sound or anything over a few hundred frames: use video
- When the frames are heavy and the audience is on slow connections
- Inside a scaled or transformed parent, which breaks ScrollTrigger's measuring

## Notes for agents

- GSAP installs from npm under GSAP's own licence. No GSAP code ships in this registry
- src is a function, so any page or demo that passes it must be a client component, or the static build fails
- Below 768px, under reduced motion and with no script it is one real img at stillFrame, and no other frame downloads
- Frames load coarse to fine, six at a time, one screen before the piece arrives. A frame that fails to load is skipped and its nearest neighbour shows
- It pins against the window, so it must not sit inside an overflow container or a CSS transform

## The maths

- Pin distance is (frames - 1) × pace, so 48 frames at 32px scroll 1504px, a little under two screens at 800px.
- Cover fit scale is max(W / iw, H / ih), and the crop is W / scale by H / scale, centred.
- The canvas buffer is CSS size × min(dpr, 2), so a 3x phone never pays for nine times the pixels.
- Stride halves each pass, 32, 16, 8, 4, 2, 1, so the first 12 of 48 frames already cover the scrub at every fourth step.

Docs: https://ui.goodtabs.co/components/image-sequence