Masonry with zero layout reflow.
Every card below has its height computed in pure JavaScript before it renders. No measuring elements in the DOM, no forced reflow on resize - just arithmetic over text widths that Pretext measured once. Drag the window and watch a hundred cards re-pack instantly.
No reflow, ever
Reading offsetHeight or getBoundingClientRect forces the browser to flush layout. Do it per card and a masonry stutters. Pretext returns heights as plain numbers, so the resize path touches the DOM zero times.
Measure once, lay out forever
prepare() does the expensive work a single time: segment the text, run the line-breaker, measure glyph widths against canvas. After that, layout() is arithmetic over cached advances - cheap enough to call on every resize frame.
Layouts the DOM can't give you
Once height is a function you control, masonry, text-flow around floated obstacles, balanced ragged edges, exact virtualization, and shrink-to-fit widths all become straightforward instead of hacks.
prepare(text, font)The costly call. Normalizes whitespace, segments graphemes, applies glue rules, and measures each segment against the canvas font engine. Run it once per string and keep the handle.
layout(prepared, width, lineHeight)The hot path. Walks the cached widths to return { height, lineCount } as pure arithmetic. Safe to call on every resize, for every card, with no layout cost.
The font string and line height passed to Pretext are kept in sync with the CSS that paints each card, so the measured height is exactly what the browser renders.
import { prepare, layout } from '@chenglou/pretext'
// 1. one-time pass per card — segment + measure on canvas
const prepared = cards.map(c =>
prepare(c.body, '15px "OpenRunde"')
)
// 2. on every resize: pure arithmetic, zero DOM reads
function pack(width) {
const cols = colHeightsFor(width)
for (const p of prepared) {
const { height } = layout(p, colWidth, 24)
placeInShortestColumn(p, height) // <- just numbers
}
}