Timer — auto-playing animation
Use
timerto declare a value variable that auto-advances — steps from min to max at a fixed cadence, then loop / pingpong / stop. The "time dimension" sibling of Interact controls — slider is manual drag, timer is auto-step. Pair with Vega chart / SVG scenes for animation.
When to use
- ✅ Demo animations (sine wave flowing / spring oscillating / dot orbiting)
- ✅ Data replay (frame-by-frame walkthrough of a time series)
- ✅ Algorithm auto step-through (replaces manual Next-clicking)
- ✅ Countdown / counter / pomodoro
- ❌ Discrete click triggers (use
button, not timer) - ❌ Very long durations (small
stepor hugemax-min→ browser render strain)
Basic syntax
\```interact
timer t 0 10 0.1 loop
template stl:
[Current time] -> [{t:.1f}s]
\```
timer <name> <min> <max> <step> <mode> declares a timer variable.
| Param | Meaning |
|---|---|
name |
Variable name |
min |
Start value |
max |
End value |
step |
Per-frame increment (~50ms per frame) |
mode |
loop / pause / once / pingpong |
Renders: auto-steps from 0 to 10, +0.1 every 50ms, then loops back to 0 (loop mode). The template displays t's current value live.
Modes
| Mode | Behavior |
|---|---|
loop |
At max, jump back to min and restart (most common) |
pause |
Default paused; user clicks Play to start |
once |
Run to max and stop |
pingpong |
min → max → min → max ... back-and-forth |
Typical frame rate: ~20 Hz (one step every 50ms). Reader-implementation specific.
Examples
Example 1: flowing sine wave
\```interact
timer t 0 6.28 0.05 loop
template vega-lite:
{
"width": 500,
"height": 200,
"data": {"sequence": {"start": 0, "stop": 6.28, "step": 0.05, "as": "x"}},
"transform": [
{"calculate": "sin(datum.x + {t})", "as": "y"}
],
"mark": "line",
"encoding": {
"x": {"field": "x", "type": "quantitative"},
"y": {"field": "y", "type": "quantitative", "scale": {"domain": [-1.5, 1.5]}}
}
}
\```
Renders: sine wave flows continuously right to left, like an oscilloscope. t increments forever; sin(x + t) shifts the whole curve's phase.
Example 2: spring oscillator (position drawn live)
\```interact
timer t 0 10 0.05 loop
template svg:
<svg viewBox="0 0 200 100">
<line x1="20" y1="50" x2="180" y2="50" stroke="#ddd" stroke-dasharray="2"/>
<circle cx="{100 + 60*sin(2*pi*t/2):.1f}" cy="50" r="6" fill="red"/>
</svg>
\```
Renders: red dot does simple harmonic motion along a horizontal line (left-right back-and-forth), period 2 seconds. SVG cx is driven live by t.
Example 3: pause mode + manual scrubber
\```interact
timer t 0 60 0.1 pause
template vega-lite:
{
"data": {"sequence": {"start": 0, "stop": {t}, "step": 0.5, "as": "x"}},
"transform": [{"calculate": "{t}", "as": "y"}],
"mark": "line",
"encoding": {
"x": {"field": "x"},
"y": {"field": "y", "type": "quantitative"}
}
}
\```
Renders: paused by default; user clicks Play to start, Pause to stop. Drag the bottom scrubber to jump to any moment. pause mode gives readers full control.
Example 4: pingpong (back-and-forth)
\```interact
timer angle 0 360 1 pingpong
template svg:
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke="#ddd"/>
<line x1="50" y1="50"
x2="{50 + 40*cos(angle*pi/180):.2f}"
y2="{50 + 40*sin(angle*pi/180):.2f}"
stroke="red" stroke-width="2"/>
</svg>
\```
Renders: red line rotates from 0° to 360° (clockwise full circle), then reverses back to 0°, then forward again ... back-and-forth. Smoother than loop's jump-back-to-min.
Plain-text fallback behavior
\```interact
timer t 0 10 0.1 loop
template stl:
[Current time] -> [{t:.1f}s]
\```
→ Non-supporting readers show as a code block — readers see the timer t 0 10 0.1 loop declaration + template, understand the original meant to animate.
| Reader | Render |
|---|---|
| Rho | Full animation + Play/Pause controls |
| GitHub web | Code block (timer declaration visible) |
| Obsidian | Same |
| VS Code default preview | Same |
| cat / less | Plain text |
Common pitfalls
1. step too small → browser hangs
timer t 0 100 0.0001 loop ← ❌ 1,000,000 frames; browser dies
timer t 0 100 0.5 loop ← ✅ 200 frames; ~30s per cycle
Rule of thumb: (max - min) / step ≈ 100-1000 frames for the most comfortable feel.
2. Multiple timers — sync issues
\```interact
timer t1 0 10 0.1 loop
timer t2 0 10 0.1 loop
\```
→ t1 and t2 are independent — phases may drift apart (reader-dependent). For strict sync, use one timer + computed derivations:
timer t 0 10 0.1 loop
computed t1 = t
computed t2 = (t + 5) % 10 ← Phase-shifted by 5s
3. Timer used as "counter" (when button was wanted)
timer counter 0 1000 1 loop ← ❌ Auto-counts forever; user wanted click +1
→ Use button + computed self-reference for manual counters.
4. mode misspelled
timer t 0 10 0.1 looping ← ❌ Not recognized
timer t 0 10 0.1 LOOP ← ⚠️ Case-sensitive
timer t 0 10 0.1 loop ← ✅
Only 4 valid modes: loop / pause / once / pingpong.
5. Timer name clashes with slider name
\```interact
slider t 0 10 5 1
timer t 0 10 0.1 loop ← ❌ Duplicate declaration
\```
→ Names must be unique. Timer names can't collide with slider/input/computed.
6. once mode stops at max with no restart
timer t 0 10 0.1 once
→ once runs to max and stops, no reset button. To "play again," add a button:
\```interact
timer t 0 10 0.1 once
button replay "Play again" 0
template ...
\```
But button-resetting-timer support varies by reader implementation. Most reliable: use loop + pause and let the user manually control.
7. Timer paired with chart but transform.calculate doesn't use the timer
\```interact
timer t 0 10 0.1 loop
template vega-lite:
{
...
"transform": [{"calculate": "sin(datum.x)", "as": "y"}] ← ❌ Doesn't reference {t}
}
\```
→ The curve never moves because calculate doesn't depend on the timer. Reference {t} in the vega expression: "sin(datum.x + {t})".
See also
- Interact controls — Manual controls (slider / button / etc.)
- Interact + Vega-Lite chart — Chart paired with timer for animation
- SVG scenes — SVG paired with timer for physics simulations
- Computed — Derived values (multi-timer sync)
- Shared state (namespace)
- Plain-text fallback principle