SnapDOMGitHub★ 8K
Labs Experiment

A live element, as text

A card with text and a spinning CSS cube. The right side is the same element redrawn as ASCII art on every frame, through the stream plugin.

TL;DR

One call: asciiStream(el, { onText }). Under it, stream() keeps a canvas in step with the card, and asciiFromCanvas(), the function behind the ascii-export plugin, turns each frame into characters. No capture result in between: the canvas goes straight from one to the other.

What you're looking at

The left card is editable HTML on a white background with solid-colored boxes, the shape the stream plugin patches best. The right pane is a <pre>. On every frame the plugin hands over its canvas, the text is recomputed and replaced. Pick a character set, change the width, or switch to color to see each character carry the pixel it came from.

Try it

Editable DOMcontenteditable + CSS 3D

Hello, text

Keystrokes and the spinning cube become patched frames, then characters.

SNAPDO
ASCII outputasciiStream()
starting…

Loading the stream plugin…

How it works

  1. stream() watches the card

    The first frame is a full SnapDOM capture. After that, a keystroke is traced to the nearest box with a solid background and only that subtree is captured and drawn into its rectangle. The cube is different: a 3D transform leaves the flow, so while it spins every frame is a full capture, capped here at 15 fps. Stop the cube (pause it from devtools, or delete it) and the stats switch to patches.

  2. asciiFromCanvas() does the pixel work

    On each repaint it downsamples the canvas to one pixel per character, maps luminance to the charset and returns a string, or colored HTML when format: 'html'. It is the same code the toAscii() export uses, exported on its own so it runs on any drawable: a stream frame, a video, your own canvas.

  3. asciiStream() is the two together

    It takes the ascii options and the stream options in one object and calls onText(text, frame) after every repaint. The handle it returns is the stream's: ready, stats, stop().

  4. The <pre> is replaced

    textContent for plain text, innerHTML for the colored version. Nothing else on the page is touched.

Copy-paste example

import { asciiStream } from '@zumer/snapdom-plugins/ascii-export'

const out = document.querySelector('pre')
const live = asciiStream(document.querySelector('#card'), {
  width: 90,
  fps: 15,
  onText: text => { out.textContent = text }
})
await live.ready
// later: live.stop()

Without the sugar it is two calls: stream(el, { onFrame: f => out.textContent = asciiFromCanvas(f.canvas, { width: 90 }) }). The same shape works for any pixel-level step on a frame, or for a video element instead of a stream.

Limits and things to watch

Patches need a solid background somewhere above the change; on a gradient card every frame is a full capture. Width above 140 columns makes the conversion visible in the frame budget on slow machines. Color mode produces one <span> per character, so keep it under 100 columns.