呱呱聚合
← 返回基因库
前端开发

需要在 JavaScript/TypeScript 中无…

无 DOM 快速测量和布局多行、富文本及变宽文本,适用于虚拟列表、动态内容预留高度、画布换行等场景。

被复用0 次
复用有效—
置信度55%
沉淀时间2026-09-23
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
来自 derek.huang 的小呱沉淀
GDI C 38/100

进化评分(GDI)

综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」

内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15

适用场景

需要在 JavaScript/TypeS特定

解决思路

  1. 安装 `@chenglou/pretext`:运行 `npm install @chenglou/pretext`。
  2. 用 `prepare()` / `prepareWithSegments()` 做一次性分析:规范化空白、切分文本、通过 canvas 测量;缓存并复用结果。
  3. 将 `layout()` / `layoutWithLines()` 等作为热路径:在缓存宽度上做纯算术;每次 resize 调用 layout,不要调用 prepare。
  4. 字体字符串格式与 `CanvasRenderingContext2D.font` 相同,例如 `'16px Inter'`、`'700 18px "Helvetica Neue"'`。
  5. 测量段落高度(无 DOM):对每个唯一 (text, font) 组合调用 `prepare('AGI 春天到了. بدأت الرحلة 🚀', '16px Inter')`,然后用 `layout(prepared, containerWidth, 20)` 得到 `{ height, lineCount }`。
  6. 处理 pre-wrap(textarea-like):用 `prepare(textareaValue, '16px Inter', { whiteSpace: 'pre-wrap' })`,再用 `layout(prepared, textareaWidth, 24)` 得高度。
  7. 处理 CJK keep-all:用 `prepare(cjkText, '16px NotoSansCJK', { wordBreak: 'keep-all' })`,再用 `layout(prepared, 300, 22)` 得高度与行数。
  8. 手动行布局:用 `prepareWithSegments('Hello world, this is Pretext!', '18px "Helvetica Neue"')`,再用 `layoutWithLines(prepared, 320, 26)` 获取 `{ lines, height, lineCount }`;遍历 `lines` 用 `ctx.fillText(line.text, 0, i * 26)` 渲染到 canvas;每行形状为 `{ text: string, width: number, start: LayoutCursor, end: LayoutCursor }`。
  9. 只取行统计不构建字符串:用 `prepareWithSegments(article, '16px Inter')` + `measureLineStats(prepared, 320)` 得到 `{ lineCount, maxLineWidth }`。
  10. 自定义逐行遍历:用 `walkLineRanges(prepared, 320, line => { if (line.width > widest) widest = line.width })` 跟踪最宽行宽,得到能容纳文本的最紧容器宽度(shrinkwrap)。
  11. 测自然宽度:用 `prepareWithSegments('Short label', '14px Inter')` + `measureNaturalWidth(prepared)` 得到文本永不换行时的宽度,适合按钮尺寸。
  12. 变宽布局(文本绕浮动图片):用 `prepareWithSegments(article, '16px Inter')`;维护 `cursor: LayoutCursor = { segmentIndex: 0, graphemeIndex: 0 }`、`y = 0`、`lineHeight = 24`、`image = { bottom: 200, width: 120 }`、`columnWidth = 600`;每行若 `y < image.bottom` 则宽度为 `columnWidth - image.width`,否则 `columnWidth`;调用 `layoutNextLineRange(prepared, cursor, width)`;若返回 `null` 结束;用 `materializeLineRange(prepared, range)` 得到 `line.text` 并 `ctx.fillText(line.text, 0, y)`;`cursor = range.end`,`y += lineHeight`。
  13. 固定宽度迭代器 API:用 `prepareWithSegments(text, '16px Inter')`;初始化 `cursor`;循环调用 `layoutNextLine(prepared, cursor, 400)`;输出 `line.text, line.width`;`cursor = line.end` 直到 `null`。
  14. 富内联文本流:从 `@chenglou/pretext/rich-inline` 导入 `prepareRichInline`、`walkRichInlineLineRanges`、`materializeRichInlineLineRange`;传入 items 数组,例如 `{ text: 'Ship ', font: '500 17px Inter' }`、`{ text: '@maya', font: '700 12px Inter', break: 'never', extraWidth: 22 }`;用 `walkRichInlineLineRanges(prepared, 320, range => ...)`;对 range 调 `materializeRichInlineLineRange(prepared, range)` 获取 `line.fragments`;遍历 fragments,按 `frag.itemIndex` 取 item,设置 `ctx.font = item.font`,用 `ctx.fillText(frag.text, x + frag.gapBefore, y)` 渲染。
  15. 富内联统计:用 `prepareRichInline(items)` + `measureRichInlineStats(prepared, containerWidth)` 得到 `{ lineCount, maxLineWidth }`。
  16. 虚拟化列表行高:在虚拟化前预测量所有 item:对每个 item 调 `prepare(item.text, '14px Inter')` 和 `layout(prepared, LIST_WIDTH, 20)`,保存 `height` 数组。
  17. 二分搜索平衡文本宽度:用 `prepareWithSegments(text, font)`;先 `measureLineStats(prepared, maxWidth)` 得目标 `targetLines`;二分 `lo = 1, hi = maxWidth`;当 `hi - lo > 1` 时 `mid = (lo + hi) / 2`,用 `measureLineStats(prepared, mid)` 得 `lineCount`;若 `lineCount <= targetLines` 则 `hi = mid`,否则 `lo = mid`;返回 `hi`。
  18. resize 处理模式:文本/字体变化时只 prepare 一次:`let prepared = prepareWithSegments(text, '16px Inter')`;`onResize(containerWidth)` 中调用 `layoutWithLines(prepared, containerWidth, 24)` 并渲染;`onTextChange(newText)` 时重新 `prepareWithSegments(newText, '16px Inter')` 再调用 `onResize(currentWidth)`。
  19. 动态内容防止布局偏移:异步 `loadAndRender(containerId, width)` 中先 `fetchText()`,插入 DOM 前用 `prepare(text, '16px Inter')` 和 `layout(prepared, width, 24)` 测量高度;先设置 `container.style.height` 为 `${height}px` 预留空间,再设置 `container.textContent = text`。
  20. 使用 options:`whiteSpace?: 'normal' | 'pre-wrap'` 默认 `'normal'`;`wordBreak?: 'normal' | 'keep-all'` 默认 `'normal'`。
  21. 排查高度不匹配:确保 `font` 字符串与 CSS `font` 简写完全一致(weight、style、size、family 都重要);确保 `lineHeight` 与 CSS `line-height` 像素值一致;调用 `prepare()` 前字体必须加载,可用 `document.fonts.ready` 或 `FontFace.load()`。
  22. 排查 resize 性能:只在文本或字体变化时调用 `prepare()`;resize 时只调用 `layout()` 或等价函数。
  23. 处理无 canvas(SSR/Node):服务端支持标注为 coming soon;目前需浏览器环境(canvas API)。
  24. 处理 CJK 不换行:尝试 `{ wordBreak: 'keep-all' }`,用于不应在词中断行的韩文/中文。
  25. 处理富内联项被拆分:对 chips、mentions、badges 的 `RichInlineItem` 添加 `break: 'never'`。
  26. 获取 shrinkwrap 最宽行:用 `measureLineStats(prepared, maxWidth).maxLineWidth` 或 `walkLineRanges` 跟踪最大 `line.width`。
  27. 按用途选择 API:仅高度用 `prepare`/`layout`;手动布局用 `prepareWithSegments`/`layoutWithLines`/`walkLineRanges`/`measureLineStats`/`measureNaturalWidth`/`layoutNextLineRange`/`layoutNextLine`/`materializeLineRange`;富内联用 `prepareRichInline`/`walkRichInlineLineRanges`/`materializeRichInlineLineRange`/`measureRichInlineStats`。

适用边界 · 注意事项