Word clouds are often embedded in dashboards, split views, and cards that change size as
users drag panels, collapse sidebars, or rotate a phone. Layout coordinates are computed for
a specific width × height — stretching an old result with CSS
won't reflow words into a new aspect ratio. The fix is straightforward: measure the container,
call layout() again, and redraw.
Typical responsive flow:
ResizeObserver or getBoundingClientRect() after a window
resize.width and height to
layout() with the same weighted items. Font bounds can be tuned per
breakpoint if needed.viewBox) to
match, then render the returned placements.
A window resize listener misses panel drags inside the page.
ResizeObserver fires whenever the element you care about changes size — the
right hook for grid columns, splitters, and flex children. Debounce the callback so layout
runs once after the user stops dragging, not on every intermediate pixel.
export function watchContainerResize(el, onResize, debounceMs = 1000) { let timer = null; const ro = new ResizeObserver((entries) => { const { width, height } = entries[0].contentRect; clearTimeout(timer); timer = setTimeout( () => onResize(Math.floor(width), Math.floor(height)), debounceMs, ); }); ro.observe(el); return () => { ro.disconnect(); clearTimeout(timer); }; }
Set canvas.width / canvas.height (or the SVG
viewBox) to the same values you pass into layout(). Height can
follow width with a fixed aspect ratio, track the container directly, or come from your
design system — as long as layout and renderer agree on pixels.
async function renderCloud(w, h) { canvas.width = w; canvas.height = h; const placements = await layout(items, { mode: "pretty", width: w, height: h, minFontSize: 14, maxFontSize: 72, padding: 2, }); drawWordCloud(ctx, placements); }
Layout is async. If the user resizes again while a pass is still running, an earlier result can arrive late and flash the wrong frame. Bump a generation counter before each call and ignore placements that come back after a newer resize has started.
let generation = 0; async function renderCloud(w, h) { const pass = ++generation; const placements = await layout(items, { width: w, height: h, /* … */ }); if (pass !== generation) return; drawWordCloud(ctx, placements); }
Random datasets at 50 or 500 words. Drag the handle on the right edge of the panel to resize the container — layout re-runs after you stop dragging (1 s debounce).
Random datasets at 50 or 500 words. Drag the handle along the bottom edge to pull the canvas taller — layout re-runs after you stop dragging (1 s debounce).
Container width: —. Presets tune
minFontSize / maxFontSize so words stay readable at each scale
(50 → 16–88px, 500 → 10–52px). Try narrowing to mobile widths — words reflow into the
new bounds instead of clipping.
Container size: —. Presets tune
minFontSize / maxFontSize so words stay readable at each scale
(50 → 16–88px, 500 → 10–52px). Pull the bottom handle to grow the canvas height — words
reflow into the taller bounds instead of clipping.