Miraiclip SDK
React components

Scrubber

A keyboard-accessible seek bar over the whole composition.

<Scrubber.*> is a seek bar: press or drag anywhere on it to seek. The played part and the knob move on the compositor, so playback doesn't re-render it every frame.

Loading demo…
Press Play, then drag the bar. Tab to it and use the arrow keys, Shift + arrows, Page Up/Down, Home and End.

Example

SeekBar.tsx
import { formatClock } from "@miraiclip/editor";
import { Scrubber, useDurationUs, useProjectState } from "@miraiclip/react";

// Inside <ProjectProvider> and <PlaybackProvider>.
export function SeekBar() {
  // Whole seconds: this label re-renders once a second while playing, not every frame.
  const second = useProjectState((s) => Math.floor(s.playheadUs / 1_000_000));
  const durationUs = useDurationUs();

  return (
    <div className="flex items-center gap-3 text-xs tabular-nums text-neutral-400">
      <span className="w-10">{formatClock(second * 1_000_000)}</span>
      <Scrubber.Root className="h-5 flex-1 cursor-pointer rounded-full outline-none focus-visible:ring-2 focus-visible:ring-sky-400/60">
        {/* The track, then the played part on top of it (Range scales from its left edge). */}
        <div className="absolute inset-x-0 top-1/2 h-1 -translate-y-1/2 overflow-hidden rounded-full bg-neutral-700">
          <Scrubber.Range className="h-full w-full bg-sky-400" />
        </div>
        {/* Thumb is a full-size layer slid by the played fraction: the knob sits at its left edge. */}
        <Scrubber.Thumb>
          <div className="absolute top-1/2 left-0 h-3.5 w-3.5 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-sky-400 bg-white" />
        </Scrubber.Thumb>
      </Scrubber.Root>
      <span className="w-10 text-right">{formatClock(durationUs)}</span>
    </div>
  );
}

Anatomy

<Scrubber.Root>      // the slider: pointer and keyboard seeking
  <Scrubber.Range /> // the played part, scaled from its left edge
  <Scrubber.Thumb>   // a full-size layer slid by the played fraction
    <div />          // your knob, at left: 0
  </Scrubber.Thumb>
</Scrubber.Root>

Parts

Root

An ARIA slider (role="slider", focusable) over the composition's length. It sets aria-valuetext to the time, for example "0:04 of 0:12".

PropDefaultDescription
stepUs1 sArrow-key step
largeStepUs5 sShift + arrow, Page Up and Page Down step
KeySeeks
→ / ↑, ← / ↓One step forward or back (Shift: a large step)
Page Up, Page DownOne large step
Home, EndTo the start or the end

It re-renders about once a second while playing, for the screen-reader value. It is position: relative; give it a height.

Range

Scaled horizontally by the played fraction, from its left edge (transform-origin: left). Size it to the full track; to round its ends, clip it with a wrapper that has overflow: hidden and the track's radius, as in the example.

Thumb

A layer at position: absolute; inset: 0, slid right by the played fraction. Put the knob inside it at left: 0 and center it with translate(-50%, …). It ignores pointer events, so presses reach the root.

Notes

  • The bar covers the composition from 0 to the end of the last clip. For a seek bar over a zoomed part of the timeline, use the timeline's <Timeline.Ruler>.
  • Pass aria-label to the root to replace the default "Seek".
  • Show the time next to the bar with a coarse selector, as in the example: useProjectState((s) => Math.floor(s.playheadUs / 1_000_000)) re-renders once a second. usePlayheadUs() would re-render every frame.

On this page