Web pages and web projects
Two layers put a web page in a scene. A Webpage layer shows a page that already exists: an address on the web, or an HTML page you have. A Web project layer is a page you write in Studio, in HTML, CSS and JavaScript, that moves to the music and the pointer and can talk to the scene's script.
Try it: Kelp Forest (a web project talking with its script).
Both are drawn like any other layer: you can place them, blend them, put effects on them and stack other layers over them. They're drawn on Linux and Windows; on Android they aren't drawn yet, and the rest of the scene plays without them.
The Webpage layer
Add a Webpage layer (it's under Backgrounds, and under Your own), starting from An address or a file. Its main setting is Address:
- an
https://address, which shows that page from the web, or - a page of your own. Click HTML file… to pick an HTML file (Studio copies it into the scene with what's beside it), or Folder… to pick a folder with an
index.htmlin it.
A page you bring in this way is copied into the scene's folder, under web/, so the scene carries it wherever it goes. Under the address, Studio says how loading went.
The other settings:
- Zoom: the page larger or smaller.
- See-through background: where the page has no background of its own, the layers under it show.
- Clicks reach the page: a click on the desktop clicks the page there too. Off, the page is only looked at.
- Frame rate: how often the page's animations may draw (15, 30, 60, or the display's own).
- Resolution: draws the page at fewer pixels and scales it up. Lighter on the computer.
- Reload every: reloads the page every so many minutes; 0 never. Good for a page of news or a dashboard.
- Mute: on by default.
- Remember sign-ins: keeps the site's cookies and storage after the desktop restarts. Off, every start is a fresh visit.
What a page may do
A page in a wallpaper can't wander off. Motion keeps to these rules:
- A web address must be
http://orhttps://. Nothing else is shown: nofile:, nojavascript:, nodata:as the page itself. - A page from the web can go anywhere on the web, as it would in a browser, but not onto your files.
- A page of your own never reads files. Studio packs it, with its scripts, styles, pictures and other files, into one page, and that's what the desktop shows. It can't reach the folder, the scene, or anything else on the disk.
- A page of your own stays where it is: it can't leave for another address. It can't load anything from the web either, unless it's a Web project with Network on.
Start a web project
Add a Webpage layer and, under Start from, pick Web project. Or click Add › Web page in Assets. It starts as a page, a stylesheet and a script (index.html, style.css, main.js), with motion.d.ts beside them.
Its files are written into the scene's folder, as web/page/ (or page2, page3 for the next ones). A scene that isn't saved yet keeps them in a draft folder, and they move with it when you save.
The layer's settings are much like the Webpage's, with a few differences:
- See-through background and Clicks reach the page start on.
- There's no Reload every or Remember sign-ins: the page is your own, and Studio reloads it when you change it.
- Hears the sound: Studio turns this on by itself when your code reads the sound, and off when it stops.
- Network: lets the page load things from the web (
httpandhttpsonly). Off by default.
Edit it in Studio
Pick the layer and click Open the editor in the inspector, or open Window › Files. The editor has three parts.
- The files, down the left. Click one to open it in a tab. Below the list: a new file (type a path such as
scripts/clock.jsto make a folder too), rename the open file, and delete it (click twice to be sure).index.htmlcan't be deleted: it's the page. - The open files, as tabs, with colours for HTML, CSS, JavaScript, GLSL and JSON. As you type, Studio offers the
motionwords the page can use. - The console, along the bottom: whatever the page logs with
console.log(), its errors, and Studio's own notes, such as a file the page asks for that isn't there. Clear empties it.
Studio writes each change half a second after you stop typing, packs the page again and reloads the preview. The line at the top says Saving…, then Saved and packed, or how many files are missing. Reload packs it again by hand.
What goes in
Studio packs the project's own files: scripts, stylesheets, pictures, fonts, sounds, videos, JSON and the rest. Everything the page refers to is put inside the packed page:
<script src>, stylesheets,src,hrefand CSSurl()that point at the project's files- JavaScript modules:
import ... from "./util.js"works - TypeScript: a
.tsfile is compiled to JavaScript, andimport "./util.js"findsutil.ts - packages in the project's
node_modulesfolder, by name:import confetti from "canvas-confetti". To get a package there, copy its folder intonode_modulesin the project's folder (Show in Explorer or Show in the file manager on the project in Assets finds it), or runnpm install canvas-confettithere if you have Node.js - any other file, read with
fetch(),img.srcormotion.asset("img/a.png")
Hidden files, links out of the folder and anything past the limits are left out.
| Limit | Most |
|---|---|
| One file | 16 MB |
| A whole project | 64 MB |
| Files in a project | 2,000 |
| A file the editor opens | 2 MB |
The page and the scene: motion
Every page in a Webpage or Web project layer gets an object called motion. It's the scene as the page sees it.
motion.on("beat", (b) => pulse(b.pulse));
motion.on("frame", (f) => draw(f.time, f.dt));
motion.on("click", (c) => burst(c.px, c.py));
const glow = motion.prop("glow", 0.5); // one of the scene's settings
What it holds:
| Name | What it is |
|---|---|
motion.time, motion.running | The scene's clock, and whether the wallpaper is playing |
motion.bass, .mid, .treble, .level | What's playing, from 0 to about 1 |
motion.beat, motion.spectrum, motion.waveform | The beat, and the sound as bands and as a wave |
motion.pointer | x and y from 0 to 1 over the layer, and active |
motion.props, motion.prop(name, fallback) | The scene's settings, the ones offered in the wallpaper's settings |
motion.layer | The layer: its name and the rest |
motion.asset(path) | A file of the project, as an address the page can use |
And its events, with motion.on(name, fn), motion.once and motion.off:
| Event | When |
|---|---|
ready | The page is loaded |
frame | Every frame, at the layer's frame rate: time, dt |
beat | A beat: count, pulse |
sound | About 30 times a second while sound comes in |
pointer | The pointer moved: x, y, active |
click | A click or tap on the desktop: x, y (0 to 1), px, py (CSS pixels) |
props | The scene's settings changed |
pause, resume | The wallpaper stopped (it's covered, say) or carries on |
Motion sends the page only what it reads. A page that never touches the sound or the pointer costs nothing for them. Draw on frame rather than your own requestAnimationFrame loop and the page keeps to the layer's frame rate and rests while the wallpaper is paused (Motion caps requestAnimationFrame to the frame rate too).
A page can be written in TypeScript too: a .ts file is compiled as the page is packed. motion.d.ts, in every new page, describes all of this for the editor.
Talking to the scene's script
A Web project's page can talk to the scene's script, both ways. (A Webpage layer showing a site can't.)
From the page:
motion.send({ wants: "rain" }); // to the script
motion.on("message", (data) => show(data)); // what the script sends
In the script, scene.on("page", (layer, data) => ...) hears it, and scene.sendToPage(layer, data) answers. In a graph, the nodes are On Page Message and Send to Page, under HTTP. What goes either way must be something JSON can hold, up to 64 kB at a time. See Logic as code and the scripting reference.
Asking the motes
A page can also reach the scene's motes through motion.stage. Each answer is a promise, answered by the scene's script, or refused if it doesn't come within ten seconds.
const motes = await motion.stage.motes(); // [{ id, name, class }]
const lamp = motion.stage.mote("lantern"); // by id or name
await lamp.set("brightness", 0.8);
const said = await lamp.call("glow", 3);
const where = await lamp.location(); // { x, y, z }
A page may get and set a mote's own properties and call its class's own methods. It can't call the methods every mote has, ones whose names start with _, or the ones the scene calls by itself (onTick, onClicked and the like).
When it's slow
A page is a whole browser page, and it costs about as much as one. Lower Frame rate and Resolution first. A page with nothing moving still costs a little every frame it draws, so 15 frames a second is often plenty for a clock or a calendar. Performance has more. Every setting is in the reference: Webpage and Web project.
Last changed 2 November 2026. Something wrong here? Tell us.