Command Palette

Search for a command to run...

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.

components.json
{
  "registries": {
    "@ikui": "https://ik-ui.pages.dev/r/{name}.json"
  }
}

Then install the component:

pnpm dlx shadcn@latest add @ikui/thumbnail-strip

Usage

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

PropTypeDefault
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
-