Thumbnail Strip
A viewport-virtualized canvas strip rendering video thumbnails along a horizontal timeline. Pairs with video-thumbnail-cache for the frame decoding.
A horizontal canvas strip that lays a video's frames out along a timeline, one frame per tile. Designed for video editor track rows and scrubbable overviews — viewport-virtualized so only the visible range is decoded and painted.
Pairs with the video-thumbnail-cache lib, which
wraps mediabunny and handles frame decoding, caching, and eviction. The
strip itself only consumes a cache reference plus the visual parameters.
Installation
The strip pulls video-thumbnail-cache (and transitively mediabunny) in
automatically — one install gives you the full pair.
One-time setup: add the @ikui registry to your components.json.
{
"registries": {
"@ikui": "https://ik-ui.pages.dev/r/{name}.json"
}
}Then install the component:
pnpm dlx shadcn@latest add @ikui/thumbnail-stripUsage
import { ThumbnailStrip } from "@/components/thumbnail-strip";
import { VideoThumbnailCache } from "@/lib/video-thumbnail-cache";const cache = await VideoThumbnailCache.fromUrl(videoUrl)
const { duration } = cache.getMetadata()!
<ThumbnailStrip
cache={cache}
duration={duration}
totalWidth={duration * pixelsPerSecond}
tileWidth={96}
tileHeight={60}
/>The strip computes secondsPerTile = (tileWidth / totalWidth) * duration
internally. To "zoom", change totalWidth. To window into a sub-range of
the video (e.g. trimmed clip), pass startOffset and a shorter duration.
The component auto-detects the nearest scrollable ancestor and only paints
the visible range plus a 200 px overscan. Frames missing from the cache are
queued onto a 150 ms debounced batch; results stream back through
cache.loadBitmaps and paint incrementally. A nearest-cached frame is
painted as a placeholder while the exact one resolves.
By default (autoFallback) the strip also decodes the first frame and tiles
it beneath the canvas, so the row is filled instantly and never flashes blank
before tiles load. Pass an explicit fallbackUrl to override the poster, or
autoFallback={false} to disable it.
Examples
Windowed sub-range
Pass startOffset plus a shorter duration to window into part of the
video — here the middle half, skipping the first quarter. One cache can back
any number of such sub-ranges (e.g. a trimmed clip), each strip painting only
its own window.
Props
| Prop | Type | Default |
|---|---|---|
cache | VideoThumbnailCache | - |
duration | number | - |
startOffset | number | 0 |
totalWidth | number | - |
tileWidth | number | - |
tileHeight | number | - |
fallbackUrl | string | - |
autoFallback | boolean | true |
objectFit | "cover" | "contain" | "fill" | "cover" |
scrollContainer | HTMLElement | null | - |
className | string | - |
style | React.CSSProperties | - |