Time Scrubber

Newphysics

Tactile physics-based time dial with spring momentum, fling kinematics, rolling blur digits, sub-pixel tick rendering, and live second-advancing ruler.

$ pnpm dlx shadcn@latest add @byphongphan/time-scrubber
Requires @byphongphan in components.json
interactive
Loading preview...

Live Interactive Stage

The Time Scrubber is a tactile physical time dial designed for precision time exploration. Dragging the ruler moves time under a fixed center mark; flinging it coasts with realistic finger momentum and settles onto the nearest step via a calibrated spring simulation.

Engineering Highlights

  • Live Second-Advancing Ruler: When seconds mode is enabled, the entire dial moves in real-time with sub-second continuous 60fps sweeping or synchronized physical 1-second ticks.
  • Rolling Digits with Motion Blur: Digit cells fade through top and bottom vertical gradient masks, smearing dynamically with directional CSS blur keyframes on every step change.
  • Unified Throw and Snap Spring: On pointer release, the final landing target is calculated immediately based on velocity and momentum carry. A single critically damped spring brings the ruler to rest with zero seam between gliding and snapping.
  • Full-Width Responsive Instrument: Scales from a compact 340px control card to an expansive edge-to-edge timeline console.
  • Micro-haptic Audio Feedback: Synthesizes tactile clicks using Web Audio API as step intervals and hours pass under the needle without external audio assets.
  • Horizontal Wheel & Trackpad Swiping: Native horizontal scroll gestures interact directly with the ruler dial.

Installation

Copy and paste the component into your project:

components/time-scrubber.tsx

Update the import paths to match your project structure.


Usage

1. Full-Width Live Clock with Seconds

Expands edge-to-edge with continuous 60fps moving ruler ticks and rolling seconds.

import { TimeScrubber } from "@/components/time-scrubber"
export function LiveFullDial() { return ( <TimeScrubber timeZone="Asia/Ho_Chi_Minh" format="12h" showSeconds={true} fullWidth={true} live={true} tickMode="smooth" showReset={true} sound={false} step={5} /> )}

2. Basic Interactive Minute Scrubber

Default 15-minute snapping step with fling momentum.

import { TimeScrubber } from "@/components/time-scrubber"
export function MinuteScrubber() { return ( <TimeScrubber step={15} momentum={50} format="12h" onChange={(mins, info) => console.log(info.time, info.mer)} /> )}

3. High-Precision 24-Hour Scrubber

Fine-grained 1-second steps with 24-hour format.

import { TimeScrubber } from "@/components/time-scrubber"
export function PrecisionScrubber() { return ( <TimeScrubber format="24h" showSeconds={true} step={1} momentum={40} start={14 * 3600} // 14:00:00 /> )}

Props

PropTypeDefaultDescription
showSecondsbooleanfalseDisplays seconds readout and activates second-precision dial ruler.
fullWidthbooleanfalseStretches edge-to-edge across the parent container.
tickMode"smooth" | "step""smooth""smooth" for 60fps continuous sweep, or "step" for physical 1s hops.
stepnumber | string5 / 15Snap increment (seconds if showSeconds, else minutes).
momentumnumber50Fling throw coast factor from 0 to 100.
format"12h" | "24h""12h"Display time format (12h or 24h).
cornernumber20Border radius in pixels.
startnumber | "now"undefinedInitial time offset. Defaults to current local/timezone time.
valuenumberundefinedControlled value.
livebooleanfalseAutomatically advances with real time when untouched.
timeZonestringundefinedIANA timezone string (e.g. "Asia/Ho_Chi_Minh", "America/New_York").
soundbooleantrueSynthesizes subtle mechanical click audio on tick crossing.
showResetbooleanfalseDisplays a "Now" button when scrubbed away from current time.
showHoursbooleantrueShows hour / minute labels under the ruler ticks.
variant"default" | "outline" | "card" | "ghost""default"Visual style variant.
onChange(value, formatted) => voidundefinedFired on time changes with value and formatted text.

Command Palette

Quick search and commands navigation