Raster basics
Raster ops run your element through a WebGL shader. Unlike CSS filters they can read neighbouring pixels, so they can halftone, dither, warp and displace — and they can be driven by the pointer, scroll or time.
A pipeline is a plain array of nodes. It is data, not calls:
let raster = [
{ op: "hexalize", size: 14 },
{ op: "duotone", colors: ["#0b1b2b", "#e6e9ed"] }
];
Attach it with the raster option on any component that accepts one:
new Wrapper()
.set({ width: "420px", height: "260px", raster })
.add([ new Text("Hello") ])
.render("#mount");
Two backends
The pipeline picks one of two backends at runtime.
Snapshot is the default everywhere. The element is captured once and the canvas is painted over it as a purely visual overlay. The real DOM stays where it was, so text is still selectable underneath, but the picture does not update when the content changes.
Live uses the HTML-in-Canvas API (texElementImage2D, currently behind
chrome://flags/#canvas-draw-element). The host's children move inside
the canvas, so they keep layout, interactivity and their place in the
accessibility tree while the shader paints them.
import { isHTMLInCanvasAvailable } from "nodality";
isHTMLInCanvasAvailable(); // true when the live backend is usable
You can opt a single node out of the live path:
{ op: "hexalize", size: 14, live: false }
The distinction matters more than it sounds. Bugs that only appear in live mode are common, because that is the only mode where the children leave the host — if a host centres its child with flex, the child has to keep that centring after the move. Test both.
The host needs a real box
Give the host an explicit width and height.
The pipeline appends its canvas into the host and sizes it from the host's measured box. A host that sizes itself to its own content therefore grows every frame: the canvas makes the box bigger, the bigger box makes a bigger canvas. On a text link this takes the layout to infinity in a few frames.
// Safe: the box is stated, so the measurement is stable.
new Wrapper().set({ width: "212px", height: "54px", raster })
// Unsafe: the host sizes to its content and the canvas feeds that growth.
new Link("Open the app", "/app").set({ raster })
State the height too, not just the width. In live mode the pipeline moves the children into a div sized from the measured box, and a fractional content height gets pinned a pixel short — enough to clip the bottom of a rounded button.
Elements that cannot host a pipeline
Some elements cannot contain rendered children, so appending the canvas to them puts it in the DOM where it is never painted. The effect appears to attach and then does nothing:
IMG VIDEO CANVAS IFRAME INPUT EMBED OBJECT
TEXTAREA SELECT BR HR AREA SOURCE TRACK WBR
To rasterise an image, wrap it: put the <img> inside a Wrapper and give
the wrapper the pipeline.