Three interactive Canvas 2D experiences built on @chenglou/pretext — the pure TypeScript text measurement engine that bypasses DOM reflow entirely.
Live demo: https://pretext-playground-upg.vercel.app
Every character on screen is a physics body. No DOM text elements. No CSS layout. Just pretext-measured positions and real-time simulation.
A physics-driven ASCII serpent that glides through multilingual text. Letters scatter and spring back. Hold click to breathe fire. Shoot enemies for points.
Ice Dragon preset — click and hold to freeze letters in place. Letters accumulate cracks from dragon collision and shatter into ice shards. Enemies freeze solid then shatter after 1.5 seconds. Aurora borealis wisps drift across a deep blue sky.
A deep ocean canvas with bioluminescent jellyfish drifting through text layers. Click to send sonar pulses that push everything away. Lanternfish scatter in schools. A manta ray hunts them with ASCII letters forming its body.
Five animated ASCII art scenes: Matrix Rain, Wave Text, Morph Text, Particle Text, and Typewriter.
Pretext solves one of the oldest bottlenecks in web UI: measuring text dimensions requires DOM access (getBoundingClientRect), which triggers expensive reflows. Pretext's prepare() does a one-time canvas measurement, then layout() is pure arithmetic — ~0.0002ms per call. This unlocks text-heavy UIs at 120fps without ever touching the DOM.
import { prepare, layout } from '@chenglou/pretext'
const prepared = prepare(text, '16px Inter')
const { height, lineCount } = layout(prepared, containerWidth, 20)
// No DOM. No reflow. Pure math.| Feature | Details |
|---|---|
| Dragon | 60-segment ASCII chain (◆▓▒░╬║│·) with wings, spines, eyes, fire breath |
| Ice Dragon | Freeze letters → accumulate cracks → shatter into ice shards. Aurora borealis, frost particles, cyan cursor |
| Physics letters | Every character is an independent physics body with velocity, rotation, spring-home force, dragon collision |
| Fire / Ice breath | Hold click to emit particles — fire blasts letters away with burn timers, ice freezes them in place |
| Enemies | 4 types: Grunts (1 HP), Tanks (3 HP), Fast (dart unpredictably), Ghosts (2 HP, sine wave) |
| 3D tunnel | Perspective-projected multilingual text rings scrolling into the distance |
| Runes | Floating ambient kanji/kanji symbols drifting upward |
| Control panel | Press P — 6 presets (Default, Gentle, Chaos, Zen, Tiny, Leviathan, Ice) + granular sliders |
| Feature | Details |
|---|---|
| Jellyfish | 4 types with outlined bell shapes, pulsing animation, bioluminescent glow |
| Tentacles | Spring-chain particle system attached to jellyfish bells, react to sonar pulses |
| Sonar pulses | Click to send expanding shockwave rings that push letters and jellyfish |
| Lanternfish | Schools of small fish with flocking AI, scatter from sonar, flee from manta |
| Manta ray | ASCII letters form the manta body, hunts lanternfish with star-shaped glow |
| Caustics | Layered sine wave grid simulating underwater light patterns |
| Bubbles | Rising, wobbling bubble streams originating from ocean floor rocks |
| Ocean floor | Rock formations at the bottom of the canvas |
| Sediment | Ambient drifting particles settling from above |
| Control panel | 2 presets (Surface, Twilight Zone) + 11 independent layer toggles |
| Scene | Description |
|---|---|
| Matrix Rain | Green characters falling in columns, classic terminal aesthetic |
| Wave Text | Large text block undulating with sine wave displacement |
| Morph Text | Characters transitioning between different words |
| Particle Text | Thousands of particles forming readable text shapes |
| Typewriter | Characters appearing one at a time with cursor |
pnpm install
pnpm devOpen http://localhost:5173 and pick a tab.
| Input | Action |
|---|---|
| Mouse move | Dragon follows cursor |
| Click & hold | Breathe fire (Default) or ice (Ice preset) |
P |
Toggle control panel |
Esc |
Close panel |
| Input | Action |
|---|---|
| Mouse move | Tracks position for jellyfish AI |
| Click | Send sonar pulse (pushes letters + jellyfish) |
P |
Toggle control panel |
Esc |
Close panel |
src/
├── dragon.ts ─ Dragon page: physics, fire/ice, enemies, particles,
│ 3D tunnel, runes, UI binding, main loop (~800 lines)
├── dragon-page.ts ─ Control panel HTML builder (sliders, toggles, presets)
├── config.ts ─ Shared config object + presets (jellyfish)
├── jellyfish.ts ─ Jellyfish orchestrator: wires all systems together
├── jellyfish-page.ts ─ Jellyfish control panel HTML builder
├── jellyfish.css ─ Jellyfish-specific styles
├── shared.css ─ Shared nav + panel styles
├── systems/ ─ Jellyfish modular systems (all SoA, zero-GC)
│ ├── sonar.ts ─ Click → expanding ring, pushes entities
│ ├── text.ts ─ Pretext layout + SoA letter arrays + spring physics
│ ├── jellyfish.ts ─ Bell outlines, glow, pulsing, 4 types, AI movement
│ ├── tentacles.ts ─ Spring-chain tentacle particles anchored to bells
│ ├── lanternfish.ts ─ Schooling fish with flocking AI + fear trails
│ ├── manta.ts ─ ASCII letter manta ray, hunts lanternfish
│ ├── atmosphere.ts ─ Caustics, bubbles, sediment
│ └── oceanFloor.ts ─ Rock formations at canvas bottom
└── shared/utils.ts ─ Shared utility functions
Performance patterns used throughout:
- Structure-of-Arrays (SoA) — parallel
Float32Arrays, no objects per entity - Swap-remove — O(1) particle/entity deletion, no
splice/filter - Pre-allocated pools — fixed max buffers, zero GC in the game loop
- Cached measurements — pretext layout computed once at init/resize, never in the hot path
- Batched
ctxstate —prevFonttracking minimizesctx.fontassignments - DPR capped at 2 — prevents OOM on high-res displays
- Delta time capped at 50ms — prevents physics explosion after tab switch
- @chenglou/pretext — text measurement engine
- Canvas 2D — all rendering, zero DOM text elements
- Vite — dev server and bundler (MPA with 3 entry points)
- TypeScript — strict mode throughout
This project is open source under the MIT License. Contributions, forks, and community experiments are welcome. If you build something interesting on top of pretext, consider sharing it — that's how this project started.
MIT





