Skip to content

Scripts

Draw the cursor's path once and play it on every take: the editor, what each point does, and the JSON a script is kept as.

@cesarvenzac/closeupv1.0.0

A script moves the drawn cursor for you: the same path, at a hand's pace, on every take. The page hears it as it would a mouse, so menus open and hover effects follow.

The editor

Script the cursor, in the toolbar (videos only, once something is picked), opens the editor on the stage, at the size the take is filmed:

  • Click where the cursor starts, then each place it goes. A click makes a gentle arc, as a wrist moves; a drag pulls the curve's handles, as Figma's pen does.
  • Drag points and handles to adjust them, ⌥ to break a handle's mirror, and double-click a point to switch between handles and an arc. The arrow keys nudge the point chosen.
  • The editor shows the page as it is at the point chosen, scrolled as far as the points before scroll it, so the next point goes on what's shown.

What a point does

  • Move rests there: hovering, to show what hovering does.
  • Click clicks there.
  • Type clicks and types into the field that click focused, a character at a time: put it on a field.
  • Scroll turns the wheel there: what scrolls under it scrolls, or the page, the cursor keeping still while the page moves under it.

A click, typing or a scroll can hover a moment before it acts, and every point can rest after. The move there takes a hand's time, by Fitts's law with a minimum-jerk speed, unless you set it.

A point is tied to the element it's placed on, so it follows that element if the layout moves.

Preview, then film

Preview plays the script on the page without filming. Every change is kept for this page as you make it, in the browser; Done leaves the editor and sets the cursor to move by script, so Record plays it and stops by itself. Undo, redo, import and export are in the editor's bar: export the JSON to keep it with the page.

While a script plays, the page is told about every move and press as a mouse would tell it, pointer and mouse events and focus, so its own code reacts. CSS's :hover and :active follow only the real pointer, so every rule that uses them is copied with an attribute in their place, set on what the cursor is over. Your mouse is kept off the page meanwhile.

The format

A script is plain JSON, to keep with the page:

{
  "version": 2,
  "start": { "x": 480, "y": 130 },
  "steps": [
    {
      "to": {
        "x": 450,
        "y": 110,
        "on": { "selector": "button.like", "u": 0.5, "v": 0.5 }
      },
      "action": "click",
      "hover": 0.4,
      "wait": 1
    },
    { "to": { "x": 600, "y": 300 }, "action": "type", "text": "Hello", "wait": 0.6 },
    { "to": { "x": 600, "y": 300 }, "action": "scroll", "scroll": 400, "wait": 0.5 }
  ]
}
  • start and each step's to are points in page pixels, as laid out rather than enlarged; on names the element and where in its box, from 0 to 1 across and down.
  • action is move, click, type or scroll; text is what type types, and scroll how far scroll scrolls in page pixels, down when positive.
  • hover and wait are seconds before acting and after; duration is the move's, a hand's pace when left out.
  • in and out are a point's handles, relative to it; without them the move is a gentle arc.
  • version is 2 once a step scrolls; a script that doesn't scroll is written as version 1, which Closeup 0.x reads too.

In code

parseScript checks a script and returns it, or null if it isn't one; saveScript and loadScript keep one for a page, by its path, in the browser. A take plays the page's script once the cursor is set to move by script (Options, Cursor, Moves; Done in the editor sets it). To ship a scene with your page:

import { parseScript, saveScript } from "@cesarvenzac/closeup";

const file = await fetch("/takes/like.json");
const script = parseScript(await file.json());
if (script) saveScript(script); // played when Moves is Script