morphicons

Showcase

canvasTarget turns any canvas or 2D context into a morph target. The icon stops being a DOM node and becomes pixels you own: a GPU texture inside a map, a sprite composited into a chart, a favicon, an OffscreenCanvas in a worker.

Why this path
Icons that live where the DOM cannot reach: WebGL scenes, canvas charts, workers, video frames. However many icons fly at once, they share the driver's single rAF.
How it works
Path2D parses each frame's geometry, so a write is one stroke() call. Hosts with their own render loop never hand over their context: the icon draws on a small dedicated canvas, the host composites the pixels, and onWrite is the dirty signal.
The tradeoff
You own resolution and devicePixelRatio. Color is fixed at target creation, so a live tint happens at composite time (both demos below do exactly that). And the heavy megabytes are the hosts, not the adapter: canvasTarget is 0.5 KB gzip.

A streaming chart · TanStack Charts

The trend indicator is a morph riding the series: when it turns, the shape and the color turn with it, and icon, line and area share one accent driven by the data. Nudge the drift and watch the flip.

Map pins with states · Mapbox GL

Every pin is a Mapbox StyleImage whose pixels come from a morphing 2D canvas: place it, watch it learn its address, hover it, save it. Saving rolls a wave through the map's own texture, pins included.

The whole integration

One adapter call around the plain DOM driver. Everything else in the demos above (tinting, dirty flags, StyleImage plumbing) is host-side compositing of the pixels this produces.

morphicons/adapters
import { createMorph } from "morphicons/dom";
import { canvasTarget } from "morphicons/adapters";
import { Flag, MapPin } from "lucide"; // data, not components

const sprite = document.createElement("canvas");
sprite.width = sprite.height = 64;

const morph = createMorph(
  canvasTarget(sprite, { color: "#fff", onWrite: () => map.triggerRepaint() }),
  MapPin,
);
morph.morphTo(Flag, "smooth");

// the canvas is pixels you own: a Mapbox StyleImage, a chart badge,
// a WebGL texture, a favicon, an OffscreenCanvas in a worker

Mapbox GL and the chart runtime load only inside this tab, and only as their card scrolls into view: the core showcase stays as light as the library it demos. The save wave is Canvas UI's Ripple shader (MIT + Commons Clause), adapted to sample the map's own canvas.