Skip to content
blotter

class · blotter.ts

Blotter

The orchestrator: packs its texts into one atlas, renders them through the material in a single draw call, and hands each text its own canvas.

extends Emitter<BlotterEvents>

import { Blotter } from "blotter.ts";

Whenever you want a material applied to some texts, you make a Blotter. It measures the texts, bin-packs them into a shared texture, compiles the material into a shader that reads that texture, and runs a render loop on requestAnimationFrame. Every Blotter on the page draws through one shared WebGL context, so the cost of a second instance is a render target, not a context.

The constructor throws where WebGL is unavailable; check isWebGLSupported() first when you need a plain-text fallback.

import { Blotter, Text } from "blotter.ts";
import { ChannelSplitMaterial } from "blotter.ts/materials";

const text = new Text("Hello", { family: "serif", size: 96 });
const blotter = new Blotter(new ChannelSplitMaterial(), { texts: text });

blotter.forText(text)?.appendTo(document.body);
await blotter.ready;

Members #

constructor constructor

new Blotter ( material: Material , options?: BlotterOptions )

Takes the material to draw with and an optional BlotterOptions: the texts, the pixel ratio, and the three auto* switches. With the defaults the instance builds and starts rendering immediately.

ratio property

readonly blotter. ratio : number

The pixel ratio the atlas is drawn at. Defaults to the device pixel ratio so text is crisp on high-density screens; read-only after construction.

autoplay property

readonly blotter. autoplay : boolean

Whether new render scopes start in the playing state. Set through the options; read-only afterwards.

ready property

readonly blotter. ready : Promise<Blotter>

A promise that resolves with the instance after its first successful build, and rejects if the material fails to compile. The ready event fires at the same moment; awaiting the promise is the usual way to wait.

autostart property

blotter. autostart : boolean

Whether the render loop is running. start() and stop() flip it, so it doubles as a "is this instance animating" check.

autobuild property

blotter. autobuild : boolean

Whether the instance builds itself on construction and after changes. Set it to false in the options when you intend to add texts or swap the material before the first build, then call update() yourself.

material get / set

blotter. material : Material

The material the texts are drawn with. Assigning a new one is the same as calling setMaterial(): it takes effect after the next update().

texts get

readonly blotter. texts : Text[]

The texts this instance draws, as a read-only array. Change the set with addTexts() and removeTexts().

imageData get

readonly blotter. imageData : ImageData | undefined

The whole back buffer from the most recent frame as ImageData, or undefined before the first render. Each render scope copies its own rectangle out of this; you rarely need it directly.

setMaterial method

blotter. setMaterial ( material: Material ): void

Replaces the material. The shader is recompiled on the next update(), which you call yourself.

addText method

blotter. addText ( text: Text ): void

Adds one text. An alias for addTexts(text).

addTexts method

blotter. addTexts ( texts: Text | Text[] ): void

Adds one or more texts to be drawn. They join the atlas on the next update(), which you call yourself.

blotter.addTexts([a, b]);
await blotter.update();
blotter.forText(a)?.appendTo(el);

removeText method

blotter. removeText ( text: Text ): void

Removes one text. An alias for removeTexts(text).

removeTexts method

blotter. removeTexts ( texts: Text | Text[] ): void

Removes one or more texts. Their canvases stop updating after the next update(); remove the elements from the DOM yourself.

update method

blotter. update ( ): Promise<void>

Rebuilds the atlas and the shader from the current texts and material, and resolves when the rebuild has settled. Concurrent calls coalesce: one build runs at a time with at most one more queued, so calling it in a burst is safe. Changing a text’s value or properties, or a material’s uniforms declaration, calls this for you; you only need it after setMaterial, addTexts, removeTexts, or when autobuild is off.

start method

blotter. start ( ): void

Starts the render loop. Each frame updates the built-in uniforms, draws the atlas, and emits render.

stop method

blotter. stop ( ): void

Stops the render loop. The canvases keep their last frame. Uniform changes made while stopped are not visible until the loop runs again.

teardown method

blotter. teardown ( ): void

Disposes the instance’s render target and removes every listener it registered on its texts and material. Call stop() first. The shared WebGL context stays alive for other instances.

forText method

blotter. forText ( text: Text ): RenderScope | undefined

The RenderScope for a text this instance draws, or undefined for a text it does not know. The scope is where the canvas and the per-text uniforms live.

See also RenderScope

boundsForText method

blotter. boundsForText ( text: Text ): TextBounds | undefined

The rectangle a text occupies in the back buffer, as TextBounds. Width and height are the size of the text’s canvas; the offsets are an implementation detail of the atlas.

Inherited #

on method inherited from Emitter

blotter. on <K>( event: K , listener: Listener<BlotterEvents[K]> ): () => void

Subscribes to an event and returns a function that unsubscribes. ready fires once after the first build, update after every later rebuild, render after every frame. Every emitter in Blotter shares this shape.

const off = blotter.on("render", (frame) => {
  if (frame > 600) off();
});

off method inherited from Emitter

blotter. off <K>( event: K , listener: Listener<BlotterEvents[K]> ): void

Removes a handler added with on.