> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clayzo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# WebGL player

> The lightweight player. No setup, nothing to serve, about 250 KB compressed.

```bash theme={null}
npm install @clayzo/webgl-player
```

```ts theme={null}
import { createPlayer } from "@clayzo/webgl-player";

const player = await createPlayer({ canvas, document, loop: true });
player.play();
```

The engine is embedded in the package. There is no WASM file to stage, no CDN, and no network request other than the document's own assets.

## Options

`createPlayer` takes one options object:

| Option | Type | Description |
| - | - | - |
| `canvas` | `HTMLCanvasElement` | The canvas to draw into. Required. |
| `document` | `AnimationDocument` | The document to play. Required. |
| `loaders` | `PlayerAssetLoaders` | Image, font and audio loaders. By default assets are fetched by their `uri`. See [Fonts and assets](/authoring/fonts-and-assets). |
| `interactive` | `boolean` | Run the document's pointer bindings. Default on for documents that declare interaction. |
| `interactionElement` | `HTMLElement` | Listen for pointer events on this element instead of the canvas. |
| `loop` | `boolean` | Loop playback. |
| `muted` | `boolean` | Start without sound. Default on. See [Audio](/authoring/audio). |
| `resolution` | `"display"`, `"document"` or a number | Render at the canvas's displayed size (default), at the document's own size, or at a fixed scale. |
| `maxPixelRatio` | `number` | The highest device pixel ratio `"display"` renders at. Default `2`. See [Resolution](#resolution). |
| `pauseWhenOffscreen` | `boolean` | Stop drawing while the canvas is out of view. Default on. |
| `releaseWhenOffscreen` | `boolean` | Free the player's GPU memory while the canvas is far out of view. Default on; needs `pauseWhenOffscreen`. See [Many players on one page](#many-players-on-one-page). |
| `onFrame` | `(stats) => void` | Called after each frame the player draws, with its timings and draw counts. See [Frame timings](#frame-timings). |
| `onError` | `(error) => void` | Called when a frame fails, an image fails to load, or the GPU context is lost and can't be replaced yet. The player keeps running and retries. |

The promise resolves once fonts are loaded and the first frame can draw. Images stream in and trigger a redraw as they land.

## The handle

| Member | Description |
| - | - |
| `tick` | The current tick |
| `playing` | Whether the clock is running |
| `play()`, `pause()` | Start and stop the clock |
| `seek(tick)` | Move to a tick; resolves when the frame is drawn |
| `hasAudio` | Whether the document has audio nodes, so the page knows to offer a sound control |
| `muted`, `setMuted(muted)` | Whether sound is off, and turn it off or on |
| `setInput(name, value)` | Set a declared input: a number, boolean, point or colour |
| `fire(name)` | Fire a trigger input |
| `dispose()` | Release everything. Call it when the canvas leaves the page. |

## Fonts

Register font files before creating players or checking coverage:

```ts theme={null}
import { registerFont } from "@clayzo/webgl-player";

registerFont("manrope", await bytesFrom("/fonts/Manrope-ExtraBold.ttf"));
```

The id matches the document's font asset id. Documents packaged as `.clayzo` carry their fonts and need none of this; see [Bundles](/exporting/bundles).

## Coverage

```ts theme={null}
import { checkCoverage } from "@clayzo/webgl-player";

const { supported, reasons } = await checkCoverage(document);
```

Run it over every document you ship. See [Choosing a renderer](/playback/choosing-a-renderer).

## Many players on one page

Players share GPU contexts, so a page isn't limited by how many contexts the browser allows. Every player still needs memory for its pixels, though, and a phone closes a page that uses too much: in iOS Safari the page goes blank. The player keeps that memory bounded for you:

* **Off-screen players stop drawing.** A player whose frame hasn't changed costs nothing either.
* **Far off-screen players free their GPU memory.** Once a canvas is more than one viewport out of view, the player releases its GPU memory and its canvas's pixels. The canvas keeps its size, so your layout doesn't move. When the canvas comes within half a viewport of the screen, the player draws the same frame again before you can see it.
* **A released player keeps its state.** It keeps its document, tick, playing or paused state, inputs and pointer state. The clock doesn't run while a player is out of view, so `player.tick` is the same when it comes back as when it left, and playback continues from there.
* **Rendering is sized to the canvas.** A 1920×1080 document shown in a 320 px card renders 320 px wide, at no more than 2× the device pixel ratio.

So memory follows the players near the screen, not the number of players on the page. On a slow machine the page keeps every player moving at a lower but steady rate rather than letting one starve, and drops render resolution only when that helps.

Turn release off for a player that must reappear instantly, with no redraw:

```ts theme={null}
const player = await createPlayer({ canvas, document, releaseWhenOffscreen: false });
```

The player can only tell when the canvas is out of view, not when something covers it. If an opaque overlay covers a player, unmount the player or call `pause()`.

If the browser loses the GPU context, which can happen under memory pressure or after a driver reset, the canvas keeps its last frame. The player moves to a new context and keeps playing, and nothing is thrown.

## Frame timings

`onFrame` reports what each drawn frame cost on the main thread, in milliseconds:

| Field | Description |
| - | - |
| `totalMs` | The whole frame. Compare this against your frame budget, about 16.7 ms at 60 fps. |
| `coreMs` | Working out the frame from the document. `0` when the frame was reused. |
| `renderMs` | Drawing it. `frameMs` is the same number. |
| `presentMs` | Copying the finished frame onto your canvas. |
| `frameCached` | Whether the frame was reused rather than worked out again. |

```ts theme={null}
const player = await createPlayer({
  canvas,
  document,
  onFrame: (stats) => {
    if (stats.totalMs > 8) console.warn(`slow frame: ${stats.totalMs.toFixed(1)} ms`);
  },
});
```

The GPU's share isn't included; on a weak GPU a frame can be cheap here and still arrive late.

## Resolution

`resolution: "display"` renders to the canvas's CSS size times the device pixel ratio, capped at the document's size. `"document"` always renders at the document's size, which per-pixel effects such as dithering may want. A number is a fixed multiplier.

With `"display"`, the device pixel ratio is capped at `maxPixelRatio`, which defaults to `2`. On a 3× phone this renders, and holds in memory, less than half the pixels, with no visible difference for typical animations. Raise it for documents with fine text or hairlines that must be sharp on 3× screens, or pass `Infinity` to remove the cap:

```ts theme={null}
const player = await createPlayer({ canvas, document, maxPixelRatio: 3 });
```

If the canvas is sized by its CSS width alone, the player keeps its height at the document's aspect ratio by setting the canvas's `aspect-ratio`, unless your CSS already sets one. A canvas you size with both width and height is left as it is.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.