Closeup
Film any part of a web page in close-up, sharp at any size, with a drawn cursor and sound.
npm install @cesarvenzac/closeup// Closeup is a package (npm install @cesarvenzac/closeup), loaded only when
// it's wanted. Film it opens it on the set, ready to record.
const film = () =>
import("@cesarvenzac/closeup").then((closeup) => closeup.open({ pick: set.current }));
// It stays sharp by enlarging the page, not the take: <body> is scaled round
// the pick until the pick fills the window, so the browser draws it large at
// the screen's own resolution. The layout doesn't change, only how big it's drawn.
body.style.transformOrigin = "0 0";
body.style.transform = `translate(${stage.tx}px, ${stage.ty}px) scale(${stage.scale})`;
// Then this tab is captured and cropped to the frame by Region Capture, so
// Closeup's own controls, outside it, never show. Chromium only, for now.
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true, preferCurrentTab: true });
const [track] = stream.getVideoTracks();
await track.cropTo(await CropTarget.fromElement(frame));
// A button of your own on the finished take, as this site's Save to library is.
closeup.addAction({
label: "Save to library",
async run(take, { progress }) {
await upload(take.blob, progress);
return "Saved.";
},
});Use
import { install } from "@cesarvenzac/closeup";
install(); // ⌥⇧C opens and closes itOr load it only when it's wanted, on a shortcut of your own:
addEventListener("keydown", (event) => {
if (event.code === "KeyC" && event.altKey && event.shiftKey) import("@cesarvenzac/closeup").then((closeup) => closeup.toggle());
});Or open it on an element, ready to film, from a button of your own, as Film it does:
import { open } from "@cesarvenzac/closeup";
filmIt.addEventListener("click", () => open({ pick: document.querySelector("#demo") }));Point at an element (↑ and ↓ widen and narrow the pick) or draw a region, choose a photo or a video, and press Record; Chrome asks once to share the tab. Options set the shape, the room round the pick, a background with its inset, corners and shadow, the cursor and its click ring, the sounds and the quality, and they're kept in the browser.
Scripts
The pen opens an editor on the stage, at the size the take is filmed: click where the cursor goes, and drag to pull a curve's handles, as Figma's pen does. Each point moves there, clicks or types, at a hand's pace, and is tied to the element it's on. While a script plays, the page hears every move and press as a mouse would tell it, and its :hover and :active rules follow the drawn cursor. A script is plain JSON, to keep with the page:
{
"version": 1,
"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 }
]
}Your own actions
addAction puts a button of your own on the finished take, beside Download, to keep it somewhere:
import { addAction } from "@cesarvenzac/closeup";
addAction({
label: "Save to library",
icon: `<path d="M12 13v8"/><path d="m8 17 4-4 4 4"/>`, // optional: paths on a 24-unit grid
when: (take) => take.kind === "video", // optional: which takes to offer it on
async run(take, { progress }) {
await upload(take.blob, progress); // call progress(0…1) as it goes
return "Saved."; // said when it's done; a thrown error's message is said instead
},
});The button counts up while it works and stays pressed once it's done. addAction returns what takes it away again.
Needs
A Chromium browser on a desktop (Chrome, Edge, Arc, Brave), since Region Capture is theirs. Chrome records H.264 MP4; other builds may record WebM. Another site's stylesheets can't be read, so their hover rules can't follow a script, and the page doesn't scroll during a take. How it stays sharp is in the Lab.