Text Waterfall Cascade
Text cascades down shelves of decreasing width — a typographic waterfall.
This demo arranges text across multiple shelves, each narrower than the last. The cursor carries forward between shelves, so text flows continuously from the widest shelf down to the narrowest — like a waterfall cascading over ledges. Each shelf is centered with a slight offset to create the cascade effect.
prepareWithSegments()layoutNextLine() What this demonstrates
Cursor continuity across regions of different widths. The same prepared text flows through shelves that progressively narrow, with the cursor seamlessly carrying forward from one shelf to the next.
Relevant Pretext API
prepareWithSegments(text, font)— prepare oncelayoutNextLine(prepared, cursor, maxWidth)— different width per shelf, cursor carries between them
The waterfall metaphor
Just as water flows over ledges of decreasing width, text cascades through progressively narrower containers. This pattern is useful for funnel visualizations, stepped layouts, and any design where content must flow through regions of varying width.
Quick start
import { prepareWithSegments, layoutNextLine, buildFont } from '@chenglou/pretext';
// --- Waterfall geometry ---
const fontSize = 14;
const lineHeight = Math.round(fontSize * 1.6); // 22px
const shelfCount = 5;
const startWidth = 700; // widest shelf (top)
const endWidth = 200; // narrowest shelf (bottom)
const shelfHeight = 150; // max lines per shelf before overflow
const shelfGap = 12; // vertical gap between shelves
// --- Prepare text once, flow through all shelves ---
const font = buildFont(fontSize);
const prepared = prepareWithSegments(text, font);
let cursor = { segmentIndex: 0, graphemeIndex: 0 };
let globalY = 0;
const shelves = [];
for (let s = 0; s < shelfCount; s++) {
// Interpolate width: shelf 0 = startWidth, last = endWidth
const t = shelfCount > 1 ? s / (shelfCount - 1) : 0;
const shelfWidth = Math.round(startWidth + (endWidth - startWidth) * t);
// Center each shelf, with slight rightward cascade offset
const shelfX = Math.round((startWidth - shelfWidth) / 2 + s * 8);
const shelfLines = [];
let y = 0;
// Fill this shelf until height is exhausted
while (y < shelfHeight) {
const line = layoutNextLine(prepared, cursor, shelfWidth);
if (!line) break; // text exhausted
shelfLines.push({ text: line.text, x: 0, y });
cursor = line.end; // KEY: cursor carries to next shelf!
y += lineHeight;
}
shelves.push({
width: shelfWidth,
x: shelfX,
y: globalY,
lines: shelfLines,
});
globalY += shelfHeight + shelfGap; // advance to next shelf
}
// Result: 5 shelves, each progressively narrower
// Shelf 1: 700px wide, Shelf 2: 575px, ... Shelf 5: 200px
// Text flows seamlessly across all shelves via cursor continuity