Skip to content
Native Animation
Live Run Tracker animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add live-run-tracker
Skia

Live Run Tracker

A running tracker with a growing route and expanding session summary, with configurable route, metrics, colors, and fonts.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

About this animation

A running tracker with a growing route and expanding session summary, with configurable route, metrics, colors, and fonts.

Why this one

A static map and a stop button lose the feeling of movement. This tracker keeps the runner alive, follows the route, and makes finishing a physical gesture: the sheet expands, the map pulls back, the route turns lime to white, and the final figures count upward. No screen change is required. The recovery card and editorial chronometer give the composition its own identity.

How it works

  1. Route points are clamped and sampled at equal screen-space distances. The same progress trims the Skia path and positions the runner, keeping them aligned.
  2. A UI-thread clock advances only while running. React receives whole-second changes for metric copy; it never receives the animated route position.
  3. Each chronometer column observes its own digit. Only a changed digit rolls, and wraparound lands exactly at zero. Colons have their own narrower width.
  4. One shared sheet progress controls expansion, map framing, route completion, and the live-to-summary crossfade. Release above 40% or flick upward to commit; shorter drags spring back with the release velocity.
  5. Pause freezes the clock and route, cross-morphs the Lucide pause/play icons, dims the time, and adds a pause ring. Finishing freezes the metrics and counts the summary up from zero.
  6. The demo loops through run (6 seconds), pause (1.5 seconds), resume, finish at 9.3 seconds, summary, and reset at 14.5 seconds. The glowing route draws its opening sweep during the first 1.6 seconds; the completion transformation comes later in the specified sequence.

Install

npx expo install @shopify/react-native-skia expo-image expo-blur react-native-gesture-handler react-native-reanimated react-native-worklets

Use a GestureHandlerRootView in your app. Load your fonts before mounting. On web, initialize Skia's CanvasKit before importing the component (for example, an app-level WithSkiaWeb boundary); serve canvaskit.wasm from your public directory. The component requires no external image files, network, location permission, or real map service.

Usage

import { LiveRunTracker } from './live-run-tracker';

<LiveRunTracker
  route={[{ x: 0.2, y: 0.8 }, { x: 0.2, y: 0.3 }, { x: 0.8, y: 0.3 }]}
  metrics={{ distance: 5.24, heartRate: 174 }}
  onFinish={(run) => saveRun(run)}
/>
const tracker = useRef<LiveRunTrackerRef>(null);

<LiveRunTracker
  ref={tracker}
  fontFamily="Geist_400Regular"
  serifFontFamily="InstrumentSerif_400Regular"
  monoFontFamily="GeistMono_400Regular"
  routeSpeed={0.018}
  routeWidth={4}
  glowIntensity={0.4}
  recovery={[0.72, 0.86, 0.61]}
  labels={{ complete: 'Session saved', restart: 'Run again' }}
  onPauseChange={(paused) => syncPause(paused)}
/>
// Call these from event handlers, never during render:
tracker.current?.pause();
tracker.current?.resume();
tracker.current?.finish();

Props

PropTypeDefaultPurpose
routereadonly RunPoint[]Abstract loopNormalized points; at least two finite points. Degenerate routes remain safe.
metricsPartial<RunMetrics>38:20:12, 495 s pace, 5.24 distance, 174 heart rate, 394 elevation, 172 cadenceStarting session values. The time uses minutes:seconds:hundredths; hundredths retain the starting sample.
unitsPartial<RunUnits>mi, ft, bpm, spmDistance, elevation, heart rate, cadence suffixes. Convert input metrics and distance rate yourself.
colorsPartial<RunColors>Warm black, lime, off-whiteEntire palette: background, surface, block, street, accent, text, muted, border, clockBorder, controlInk.
labelsPartial<RunLabels>American EnglishAll structural text, weather, GPS, actions, and announcements.
formatDurationLabel(seconds: number) => stringMinutes and secondsOverride the spoken duration and unit names.
headerReactNodeNoneOptional identity beside the duration label.
fontFamily, serifFontFamily, monoFontFamilystringSystem fontInject loaded fonts; no external theme imports in the component.
initialProgressnumber0.42Starting route position, clamped to 0–0.98.
routeSpeednumber0.012Route fraction per second; stops at 0.98 until completion.
distanceRatenumber0.002Distance units added per second.
routeWidthnumber4Route stroke in points.
glowIntensitynumber0.55Route and runner glow opacity, clamped to 0–1.
recoveryreadonly [number, number, number][0.72, 0.86, 0.61]Muscles, energy, hydration levels.
onFinish(metrics: RunMetrics) => voidNoneOnce per finished run; returns frozen metrics.
onPauseChange(paused: boolean) => voidNonePause/resume transitions.
onBack() => voidNoneBack action.
style, testIDStyleProp<ViewStyle>, stringNone, live-run-trackerHost layout and stable control identifiers.

Ref: pause(), resume(), finish(), reset(). Pause/resume cannot reopen a completed run. Reset restores the starting metrics. Finish is idempotent until reset. Route/metrics props represent starting data, not a GPS subscription; integrate a real tracker in the parent.

Compatibility

Expo SDK 58; React Native 0.88.0; Reanimated 4.7.1; worklets 0.13.0; Skia 2.14.0. Uses the modules bundled in the project's Expo Go configuration. Supports iOS, Android, and web.

The parent owns safe-area insets. The component measures its container, caps the scene width at 520 pt, and scrolls a minimum-height scene inside short containers (including 300×400 and landscape). The top-right 68 pt are reserved for the catalog close button.

Accessibility

Controls have button roles, labels, actions, and stable test identifiers. Screen-reader activation finishes immediately; ordinary touch can swipe upward or hold the handle for one second. A short tap announces the finish instruction. Back and restart are standard accessible buttons. Pause/resume and completion are announced.

Reduced motion removes route drawing, runner travel, map zoom, timer blinking, and digit rolling. State transitions use 200 ms fades, and the map shows a static route. Both live and summary layers hide inactive content from assistive technology. No location data is implied by the abstract map.

Icons: Lucide (ISC). Demo identity: Tidewell from the project's supplied brand kit. No real maps, people, teams, or brand assets.

Icons use the supplied Lucide SVG paths through expo-image (inline data URIs, no network). Only the map allocates a Skia Canvas/WebGL context; keeping a separate Canvas per icon exhausts browser context limits when live and summary layers coexist. No custom runtime shader or Skia font API is used. Digit rows and the full reel have explicit heights to prevent flex compression, and each reel starts at its configured digit before animation reactions run.