DOM 测量触发的布局回流是浏览器最昂贵的操作之一。Pretext 从根本上绕过了这个问题。
使用 getBoundingClientRect 或 offsetHeight 测量文本高度时,浏览器必须同步触发布局回流。当多个组件独立测量文本,每次测量都会引发整个文档的回流。对于 500 个文本块,这可能带来 30ms+/帧 的性能开销。
Pretext 采用两阶段策略:prepare() 一次性分析并缓存文本段宽度;layout() 使用纯算术遍历缓存的宽度数据计算高度。在 resize 时只需重跑 layout(),单次调用仅需约 0.0002ms。
精心设计的 API,极致的性能,以及对全球语言的全面支持。
完全绕过 getBoundingClientRect 和 offsetHeight,通过 Canvas measureText 预计算 + 纯算术布局,从根本上消除布局回流。
prepare() 约 19ms(500 文本批量),layout() 仅 0.09ms。resize 热路径零 DOM 读取、零 Canvas 调用、零字符串操作。
基于 Intl.Segmenter 实现 CJK、泰语、阿拉伯语等所有语言的正确分词。支持 Emoji、ZWJ 序列和混合双向文本。
不仅限于 DOM,还可渲染至 Canvas、SVG、WebGL。layoutNextLine() 支持逐行变宽布局,实现文字环绕图片等复杂排版。
layoutWithLines() 返回完整行信息;walkLineRanges() 提供零字符串开销的行宽扫描,支持 shrink-wrap 和平衡文本布局。
支持 textarea 风格的文本保留:普通空格、Tab 制表符、换行符均按原样保留,适配输入框和编辑器场景。
prepare() 阶段:文本标准化、分段、glue 规则应用、Canvas 段宽测量。这是一次性的预计算工作。
layout() 阶段:遍历缓存的段宽度,纯算术计算行数和高度。这是 resize 热路径——零 DOM、零 Canvas、零分配。
两行代码即可获取精确的文本高度,无需任何 DOM 操作。
import { prepare, layout } from '@chenglou/pretext' // 一次性预计算:分析文本、分段、测量段宽度 const prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀', '16px Inter') // 热路径:纯算术计算高度,resize 时只需重跑此行 const { height, lineCount } = layout(prepared, textWidth, 20) // 🎉 就这么简单!零 DOM 回流!
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext' const prepared = prepareWithSegments( 'AGI 春天到了. بدأت الرحلة 🚀', '18px "Helvetica Neue"' ) // 获取完整的行信息 const { lines } = layoutWithLines(prepared, 320, 26) // 渲染到 Canvas for (let i = 0; i < lines.length; i++) { ctx.fillText(lines[i].text, 0, i * 26) }
import { prepareWithSegments, walkLineRanges } from '@chenglou/pretext' const prepared = prepareWithSegments(text, '16px Inter') // 零字符串分配的行宽扫描 let maxW = 0 walkLineRanges(prepared, 320, line => { if (line.width > maxW) maxW = line.width }) // maxW 即最宽行宽——精确的 shrink-wrap 容器宽度! // 这种"多行 shrink wrap"一直是 Web 中缺失的能力
import { prepareWithSegments, layoutNextLine } from '@chenglou/pretext' const prepared = prepareWithSegments(text, '16px Inter') let cursor = { segmentIndex: 0, graphemeIndex: 0 } let y = 0 // 文字环绕浮动图片:图片旁的行更窄 while (true) { const width = y < image.bottom ? columnWidth - image.width : columnWidth const line = layoutNextLine(prepared, cursor, width) if (line === null) break ctx.fillText(line.text, 0, y) cursor = line.end y += 26 }
Pretext 提供的精确文本高度是许多高级 UI 模式的关键拼图。
无需猜测或缓存的高度预测。在文本加载前就知道每个条目的精确高度,实现真正的无抖动虚拟列表。
用 JavaScript 驱动的高级布局:瀑布流、Flexbox 模拟、自定义文本排版。告别 CSS hack,用数学精确控制。
在开发阶段验证按钮标签是否溢出到下一行,无需启动浏览器。特别适合 AI 生成的 UI 组件验证。
新文本加载时精确重新锚定滚动位置,防止 CLS(Cumulative Layout Shift)。提升用户体验和 Core Web Vitals。
通过 npm 安装即用,或克隆仓库体验完整的演示和工具链。
$ npm install @chenglou/pretext # 在项目中直接导入使用 import { prepare, layout } from '@chenglou/pretext' const p = prepare('Hello World', '16px Inter') const { height } = layout(p, 300, 20)
# 克隆并启动开发服务器 $ git clone https://github.com/chenglou/pretext.git $ cd pretext $ bun install $ bun start # 打开 http://localhost:3000/demos # 查看气泡 shrinkwrap、动态布局等演示
# TypeScript 类型检查 + 代码质量 $ bun run check # 运行不变量测试套件 $ bun test # 浏览器准确性校验 $ bun run accuracy-check
# 构建 ESM 发布包到 dist/ $ bun run build:package # 打包烟雾测试 $ bun run package-smoke-test # 静态演示站构建 $ bun run site:build
从快速高度计算到完全自定义的逐行布局,按需选择合适的 API 层级。
一次性文本分析 + 测量。标准化空白、分段、应用 glue 规则、Canvas 测量段宽、返回不透明句柄。
resize 热路径。纯算术遍历缓存宽度,返回文本高度和行数。零 DOM、零 Canvas、零分配。
返回完整的行数组,每行包含 text、width、start/end 游标。适合 Canvas/SVG 逐行渲染。
零字符串分配的行宽回调。适合二分搜索容器宽度、shrink-wrap 和平衡文本布局。
迭代器风格 API,逐行布局并可为每行指定不同宽度。实现文字环绕、多栏流式排版。
clearCache() 释放内部缓存;setLocale() 设置分词器语言环境,不影响已有的 prepare 结果。
Pretext 目标是覆盖常见的 App 文本场景,以下是当前的 CSS 目标配置。
默认目标 CSS:white-space: normal、word-break: normal、overflow-wrap: break-word、line-break: auto。
传入 { whiteSpace: 'pre-wrap' } 可保留空格、Tab 和换行符。Tab 遵循默认 tab-size: 8 浏览器标准。
system-ui 字体在 macOS 上不安全——Canvas 和 DOM 可能解析为不同字体。请使用具名字体如 Inter、Helvetica 等。
由于目标包含 overflow-wrap: break-word,极窄宽度下可能在字内断行,但仅在字素边界处断开。