Filming the canvas
A canvas flight moves the camera smoothly through a document's bookmarks, and Field can record it to an MP4. The same flight replays the same way every time, so you can film a demo, change a box and film it again. Every three.js view on the canvas (a box's interactive 3D output, see 3D Output) turns slowly while the canvas flies, then goes back to its own camera.
Fly through your bookmarks
Save a few views as bookmarks (ctrl/command-shift-F1
to F9), and name them in the bookmarks panel (alt/option-B). Fly Through Bookmarks in the
command palette then flies through every saved slot in slot order. Press Escape, pan or zoom to
stop the flight where it is, or choose Stop Canvas Flight.
The flight pauses at the first bookmark, then travels to each one in turn. Each leg starts slowly, speeds up and settles. The zoom changes at an even rate, whether the canvas is at 20% or 300%, and the middle of the window travels in a straight line across the canvas. When two bookmarks are far apart and the straight line between them crosses empty canvas, the leg zooms out on the way, far enough to keep both in sight. A leg over boxes the whole way pans straight across them.
From a box
canvasFlight names the bookmarks to fly through, in order. Use the names the bookmarks panel
shows, or slot numbers such as "3". This flies through three of them, taking 4 seconds to fly to
each and holding 1.5 seconds at each:
canvasFlight(["overview", "plan", "close-ups"], legSeconds = 4.0, holdSeconds = 1.5)
The flight plays in every window open on the document. A misspelt bookmark is an error in the box, and the error message lists the bookmarks the document has.
To give each stop its own timing, pass FlightStops. A stop that leaves legSeconds or
holdSeconds out takes the flight's values. This flight has a long, slow middle leg:
canvasFlight(
FlightStop("overview", holdSeconds = 3.0),
FlightStop("plan", legSeconds = 20.0, holdSeconds = 0.5),
FlightStop("close-ups"),
legSeconds = 6.0,
holdSeconds = 2.0,
)
| Argument | Meaning |
|---|---|
legSeconds = 6.0 |
the time a leg takes to fly to the next stop |
holdSeconds = 2.0 |
the time the camera holds at each stop, the first included |
orbitDegreesPerSecond = 8.0 |
how fast each three.js view turns about its target; 0 keeps them still, and a negative rate turns them the other way |
pullBack = true |
let a leg that would cross empty canvas zoom out on the way; false flies every leg straight |
record = null |
a path to an .mp4: film the flight instead of playing it (below) |
Filming it to an MP4
Give record a path, and the flight is filmed rather than played:
canvasFlight(
["overview", "plan", "close-ups"],
legSeconds = 4.0,
holdSeconds = 1.5,
record = "~/Movies/field-flight.mp4",
)
Field renders the film one frame at a time. It sets the flight's clock to the frame's time, waits
until the window has drawn that frame, captures it, and hands it to ffmpeg. Nothing depends on how
fast the machine is: a frame that takes a second to capture shows the same thing as one that takes
ten milliseconds, so the film never drops or repeats a frame. When the file is written, the box
prints how many frames it holds and where it is. Any shot() a three.js view plays (its scripted camera move, such as the dolly,
explode and x-ray loop on 3D Output) runs on the film's clock too.
This is the flight above, filmed:
These arguments control the film:
| Argument | Meaning |
|---|---|
fps = 30 |
frames per second |
width = 1920, height = 1080 |
the film's size in pixels; null for both keeps the capture window's size |
pixelRatio = 1.5 |
pixels of film per CSS pixel of the window. At the defaults the window is 1280 by 720 CSS px, so text and box chrome read at a comfortable size in a 1080p film |
Field records in a separate capture browser sized for the film, leaving your window free. If a bookmark was saved in a window of a different size or shape, the flight adjusts its zoom to fit everything the bookmark showed.
Agents: filming the Electron app
To film Field's Electron app itself, open the capture page there first with the backdoor's
playwright-open --electron.
canvasFlight returns a CanvasFlight. durationSeconds is the flight's length, done says
whether the film is written, and result then reports the frame count, the size and, for each
bookmark, the frame that reaches it.
From the backdoor
Agents and scripts fly and film the same way with the backdoor:
uv run --project . --group backdoor python tools/field_backdoor.py --json --document my-demo \
--command 'canvas-flight overview plan close-ups --leg 4 --hold 1.5 --record /tmp/flight.mp4'
Without --record the flight plays in the document's windows. --size 1280x720,
--pixel-ratio, --fps and --orbit match the box arguments, and --expect-frames N fails the
command unless the finished file holds exactly N frames. film-page <url> <path> films any other
web page the same way, frame by frame. It takes a setup script run once and a frame script run
before each frame (scroll a column, say), and --drag DX,DY to orbit a three.js page with the
mouse.
Limits
- On a split canvas the flight moves the main (bottom-right) pane.
- The flight only moves the camera. It does not reselect boxes, reopen the editor or open and close output panels the way restoring a bookmark does.
- Only three.js views turn.
- A live flight runs on the window's own clock, so a busy machine can stutter. A filmed flight cannot.
Agents: how the film on this page is made
The docs runner builds it. The setup transcript (canvas_flight.yaml) creates the three boxes.
The drive script saves the three bookmarks through the bookmark code, as ctrl/command-shift-F1
to F3 would. ui.filmCanvasFlight then films the flight with the recorder canvasFlight(record = …)
uses, in place of the drive's real-time recording. The canvas-flight UI regression checks the
same recorder: one frame per tick, each bookmark reached exactly where restoring it puts the
camera, and the three.js view turned at the requested rate and given its camera back afterwards.