Domotion

Typing & tap overlays

Add typed strings and click ripples on top of captured frames.

Overlays let you show actions that aren't easy to capture as plain HTML states — a user typing into a search box, a finger tap on a button. They sit on top of the captured frame for the duration of that frame.

The two overlay kinds are typing (a string revealed character by character) and tap (a Material-style ripple). Both are added via the overlays array on an AnimationFrame.

Recipe: terminal typing demo

This builds a two-frame animation showing the user type a command, then its output appearing.

const WIDTH = 720, HEIGHT = 240;

// Frame 1 is an empty terminal prompt; the typing overlay drives the action.
const emptyPrompt = `<!doctype html>
<html><body style="margin:0;background:#1e1e2e;font-family:'SF Mono',monospace;color:#e6edf3">
  <div style="padding:18px;font-size:13px;">
    <span style="color:#28c840;font-weight:700;">$</span>&nbsp;
  </div>
</body></html>`;

// Frame 2 is the prompt with the command + output already painted.
const withOutput = `<!doctype html>
<html><body style="margin:0;background:#1e1e2e;font-family:'SF Mono',monospace;color:#e6edf3">
  <div style="padding:18px;font-size:13px;line-height:1.7;">
    <div><span style="color:#28c840;font-weight:700;">$</span> npm install domotion-svg</div>
    <div style="color:#56d364;">+ domotion-svg@0.1.0</div>
    <div style="color:#8b8fa3;">added 12 packages in 1.4s</div>
  </div>
</body></html>`;

// Capture both as usual, then attach a typing overlay to frame 1.
frames.push({
  svgContent: elementTreeToSvg(tree0, WIDTH, HEIGHT, "f0-"),
  duration: 2200,
  overlays: [{
    kind: "typing",
    text: "npm install domotion-svg",
    x: 36, y: 35,            // just to the right of the $ prompt
    fontSize: 13,
    color: "#e6edf3",
    speed: 45,                  // ms per character
    delay: 300,                  // ms before typing starts
  }],
  transition: { type: "crossfade", duration: 350 },
});

frames.push({
  svgContent: elementTreeToSvg(tree1, WIDTH, HEIGHT, "f1-"),
  duration: 3500,
});

Tuning the typing

  • x, y are the SVG coordinates where the text starts. Look at the captured frame's SVG to find them — typing happens in the same coordinate space as the captured content.
  • speed defaults to 60 ms/char. 30–45 feels brisk (good for short commands); 70–90 feels deliberate (good for showing a longer search query).
  • delay defaults to 300 ms. Bump it if the captured frame should "rest" before the typing starts.
  • bgColor lets you mask placeholder text in the captured input — set it to the input's background color and pad bgWidth / bgHeight to cover the placeholder.

Recipe: tap ripple on mobile UI

Tap overlays are a Material-style ripple — useful when the captured frame shows a "before" state and you want to communicate "user tapped here" before crossfading to the "after" state.

frames.push({
  svgContent: elementTreeToSvg(beforeTree, 390, 844, "a-"),
  duration: 1800,
  overlays: [{
    kind: "tap",
    x: 320, y: 760,             // center of the FAB
    delay: 900,                  // fire ~halfway through the frame
  }],
  transition: { type: "crossfade", duration: 280 },
});

frames.push({
  svgContent: elementTreeToSvg(afterTree, 390, 844, "b-"),
  duration: 2200,
});

The ripple is white-translucent and works against most backgrounds. For light backgrounds, a slightly larger frame's delay (~300 ms before the crossfade starts) makes the tap feel more intentional.

Stacking overlays

You can put multiple overlays on a single frame — typing in the search box, then a tap on the suggestion that appears:

overlays: [
  { kind: "typing", text: "design system", x: 100, y: 80, speed: 50 },
  { kind: "tap", x: 140, y: 160, delay: 1500 },
]

When to capture state into HTML instead

Overlays are convenient but they have two costs:

  1. They disable the merge fast path. Even one overlay forces every frame to render atomically, which inflates output size and may produce one-frame flicker between crossfaded frames.
  2. They live in their own coordinate space. If the captured layout shifts (different viewport size, font metric change), the overlay coordinates don't move with it.

Where the action you want to show could be expressed as captured HTML — "input filled in", "button pressed" — prefer that.

See also