Skip to content
blotter

Guide

Getting started

Install blotter.ts, put your first text on a canvas, and learn what happens underneath.

On this page

Why Blotter #

CSS runs out of tricks for text quite quickly. You get shadows, gradients, a clip-path if you are feeling brave, and then you are exporting PNGs. Blotter takes a different route: it hands each word on your page to a fragment shader and draws it, every frame, on the GPU. The text stays typographic. Your font, your size, your weight. Only the pixels are up for negotiation.

The trade is that what comes back is a canvas, not a DOM text node. Nobody can select it and screen readers will not see it, so keep the real text nearby. Blotter is for titles, headings, logotypes and words used as graphics. It is the wrong tool for a paragraph.

Install #

Blotter renders through three and leaves it to you to install, so the two go in together.

npm install blotter.ts three

The package is an ES module with types. Everything comes from two entry points: the core classes from blotter.ts, and the ready-made effects from blotter.ts/materials. Import what you use; the rest is shaken out of your bundle.

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

Your first text #

A Blotter needs two things: at least one Text, and a Material that says how to draw it. The plain Material applies no effect at all, which makes it the right place to start: if the word below looks like the word you typed, the pipeline is working.

Live · edits run as you type
import { Blotter, Material, Text } from "blotter.ts";

const text = new Text("observation", {
  family: "'Castoro', serif",
  size: 64,
  fill: "#171717",
  paddingLeft: 20,
  paddingRight: 20,
});

// The plain Material draws the text exactly as described.
const blotter = new Blotter(new Material(), { texts: text });

blotter.forText(text)?.appendTo(output);
await blotter.ready;

What just happened #

Four objects took part, and you will meet them again on every page of this guide.

  • A Text is a string plus the styles to draw it with. Blotter measures it and rasterises it once, at construction.
  • A Material is a fragment shader with named uniforms. Every text that shares a material is drawn by the same program.
  • The Blotter packs its texts into one texture atlas, renders the whole atlas through the material in a single WebGL draw call, and keeps doing so on requestAnimationFrame. There is one WebGL context for the entire page, however many Blotters you create.
  • A RenderScope is what you get back from blotter.forText(text): a per-text canvas that copies its own rectangle out of the shared buffer each frame, plus a place to set uniforms for this text alone.

blotter.ready resolves after the first build. Nothing needs it to be awaited before appending a canvas, but anything that reads per-text uniforms or the rendered imageData should wait for it.

Fonts come first #

Because a text is rasterised the moment it is constructed, the font has to be there already. Build a Blotter while a web font is still downloading and you will get the fallback face baked into the atlas. The fix is one line: ask the font loading API for the exact face, weight and size you are about to use.

const text = new Text("observation", {
  family: "'Castoro', serif",
  size: 64,
});

// Load the exact face, weight and size Blotter is about to draw with.
await document.fonts.load('400 64px "Castoro"', text.value);
await document.fonts.ready;

const blotter = new Blotter(material, { texts: text });

Where next #

Texts and uniforms covers styling, changing a text after the fact, and turning the dials on a running effect. The materials pages show the five bundled effects with live controls. When none of them is quite right, custom materials walks through writing your own, and the API reference lists everything the package exports.