前端做聊天气泡、瀑布流、富文本卡片和动态排版时,最难的往往不是把字显示出来,而是提前知道文本会占多高。传统方案通常依赖 DOM 读值,性能和准确性都容易出问题。Pretext 把这件事拆成可预计算和可复用两段,给出了可跑、可测、可验证的文本布局方案。本文会把它的核心思路、关键 API、适用场景、边界条件和落地方法一次讲透。

大家好,我是 iDao。10 年全栈开发,做过架构、运维,也在落地 AI 工程化。这里不搞虚的,只分享能直接跑、能直接用的代码、方案和经验。内容包括:全栈开发实战、系统搭建、可视化大屏、自动化部署、AI 应用、私有化部署等。关注我,一起写能落地的代码,做能上线的项目。
一、为什么这个项目突然被大量前端盯上了
问题现象
你只要做过下面这些界面,基本都碰过同一类问题:
- 聊天消息高度要先算出来,虚拟列表不能靠猜
- 标题要绕开图片或障碍物,CSS 做不到业务想要的效果
- 多语言按钮文案切换后,偶发换行导致布局抖动
- 瀑布流卡片高度依赖真实渲染结果,滚动时频繁读 DOM
根因分析
很多项目现在还在用 getBoundingClientRect()、offsetHeight 这类 DOM 读值去拿文本高度。单次看没什么,一旦和样式写入、状态更新交错,浏览器就可能被迫同步做 layout 和 reflow。这类开销在长列表、富文本、响应式场景里会非常明显。
Pretext 的定位很直接:它不是一个简单的排版小工具,而是一个"绕开 DOM 测量热路径"的文本布局库。它的核心目标不是把文字画出来,而是让你在不依赖实时 DOM 读值的前提下,稳定预测文本布局结果。
解决步骤
官方仓库给出的描述是:
- 纯 JavaScript/TypeScript 的 multiline text measurement 与 layout 库
- 支持多语言、emoji、混合双向文本
- 核心目标是绕开 DOM 测量,例如
getBoundingClientRect、offsetHeight
安装很简单:
bash
npm install @chenglou/pretext
验证方式
这个项目当前 GitHub 星标已经超过 24k,说明它击中的不是边缘需求,而是前端长期存在、但一直没有被优雅解决的问题。
二、它最值钱的设计,不是"算宽度",而是把热路径拆干净了
问题现象
不少文本测量方案也能算宽度、算高度,但一到窗口 resize、容器宽度变化、多语言切换,性能就开始掉。原因通常不是算法本身,而是每次变化都把整段文本重新测一遍。
根因分析
文本布局其实包含两类成本:
- 一次性成本:分段、空白处理、Canvas 测宽、缓存
- 高频成本:容器宽度变化后重新计算行数和高度
如果这两类成本混在一起,任何 resize 都会把重活重新做一遍。
解决步骤
Pretext 的核心 API 是两段式:
ts
import { prepare, layout } from '@chenglou/pretext'
const prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀', '16px Inter')
const { height, lineCount } = layout(prepared, 320, 20)
console.log(height, lineCount)
这里的分工很重要:
prepare():做一次性分析和测量layout():只基于缓存结果做纯算术布局
官方文档明确说明,不要在同样的文本和配置上反复执行 prepare()。例如窗口宽度变化时,应该只重新执行 layout()。
关键参数说明
有 3 个参数必须认真对齐:
font这里不是随便传一个字号字符串,而是要和真实 CSS font 简写保持一致,包括字号、字重、字体族maxWidth传入的是文本真实可用宽度,不是父容器的大概宽度lineHeight必须和页面里真实使用的line-height一致,否则高度一定会偏
验证方式
官方 README 给出的当前基准快照里:
prepare()处理共享的 500 段文本,大约是 19mslayout()对同样批次大约是 0.09ms
这个数据最关键的意义,不是"某个绝对值很快",而是它把重活放在前面,把热路径做轻了。
三、真正让它和普通测量库拉开差距的,是第二组 API
问题现象
很多业务不是只想知道"这一段文本多高",而是想知道"每一行怎么断、怎么排、能不能自己控制"。
比如:
- 消息气泡希望在不增加行数的情况下尽量缩窄宽度
- 标题要绕开图片走不同宽度的路径
- Canvas、SVG、WebGL 场景要自己控制逐行绘制
- 富文本里 inline chip、链接、代码片段要一起参与布局
根因分析
传统 DOM 方案通常只能拿到最终结果,很难把布局过程作为业务可用的 API 暴露出来。你能拿到高度,但拿不到每一行的断点、宽度、游标位置,更别说按变化宽度逐行布局。
解决步骤
Pretext 额外提供了一组更强的 API:
prepareWithSegments()layoutWithLines()walkLineRanges()layoutNextLine()
例如逐行按变化宽度布局:
ts
import { prepareWithSegments, layoutNextLine } from '@chenglou/pretext'
const prepared = prepareWithSegments(
'A floated image changes each line width',
'16px Inter'
)
let cursor = { segmentIndex: 0, graphemeIndex: 0 }
let y = 0
while (true) {
const width = y < 120 ? 240 : 360
const line = layoutNextLine(prepared, cursor, width)
if (line === null) break
console.log(line.text, line.width)
cursor = line.end
y += 24
}
这类能力意味着它不只是服务 DOM,也能服务 Canvas、SVG,甚至未来更适合服务端布局场景。
验证方式
案例站点已经把这些能力做成了可直接观察结果的页面,包括:
- Accordion
- Bubbles
- Dynamic Layout
- Editorial Engine
- Masonry
- Rich Text
- Justification Comparison
这点很重要。它不是 README 里的纸面 API,而是已经对应到具体可见的布局效果。
四、这个项目最厉害的地方,不是"有想法",而是它做了足够重的验证
问题现象
文本布局最怕的,不是功能不够,而是看起来能用,实际一上多语言、多字体、多浏览器就开始错。中文、日文、阿拉伯文、泰文、缅甸文、emoji、软连字符、混合双向文本,一旦混在一起,很多局部优化都会失效。
根因分析
文本布局不是一个单纯算法题。它背后混着:
- 浏览器字体引擎差异
- 各语言的断行规则
- 空白处理
- 标点粘连规则
- emoji 和 grapheme 行为
- 浏览器特定 quirks
也正因为这样,很多"看起来更准"的方案,在真实浏览器和真实语料里并不一定成立。
解决步骤
Pretext 的研究日志里有几个结论非常有参考价值:
-
layout()必须保持 arithmetic-only也就是热路径里不回头做 DOM 读值,不回头重测完整字符串。 -
更可靠的修正,优先放在
prepare()包括预处理、标点 glue 规则、空白处理、分段策略,而不是把逻辑越堆越多地塞回热路径。 -
有些路线试过之后被明确放弃了比如:
- 在
layout()中重建字符串再测量 - 用隐藏 DOM 做准备阶段测量
- 用 SVG
getComputedTextLength() - 把更多"聪明逻辑"塞回高频路径
- 在
-
system-ui 不是安全选择 官方研究明确指出,在 macOS 上,Canvas 和 DOM 对
system-ui的解析可能不一致。要追求准确性,应使用命名字体。
验证方式
这个项目并不是"作者说它准",而是把验证体系做出来了。开发脚本里能看到一整套校验流程:
bash
bun install
bun start
bun run check
bun run accuracy-check
bun run benchmark-check
bun run pre-wrap-check
bun run corpus-check
这意味着:
- 有浏览器准确性校验
- 有性能基准校验
- 有语料回归校验
- 有特定模式,例如
pre-wrap的专项验证
这类工程化验证,才是这个项目真正值得高看一眼的地方。
五、它最先适合落地的,不是"花哨排版",而是三类高回报场景
问题现象
很多人第一次看到 Dynamic Layout、Editorial Engine 这种 demo,会先被视觉效果吸引。但大多数团队最先能吃到收益的,并不是这些高级排版,而是那些本来就会频繁测量文本高度的普通业务组件。
根因分析
高回报场景的共性很简单:
- 文本多
- 尺寸变化频繁
- 现在依赖 DOM 读值
- 一旦卡顿,用户感知很强
解决步骤
我更建议优先从下面 3 类场景试:
1. 虚拟列表和消息流
例如 IM、评论流、通知流。文本高度如果能提前算出来,就能减少滚动过程中的实时测量和反复布局。
2. 聊天气泡和多行卡片
案例页里的 Bubbles 非常典型。它展示的不是"消息能换行",而是"能在保持行数不变的前提下,把宽度收得更紧"。
3. 富文本卡片和编辑器周边布局
例如标签、链接、代码片段、chips 和正文混排。Pretext 的 richer layout API 更适合做这类需要细粒度控制的场景。
验证方式
最简单的验证方法,不是先接整个项目,而是拿一个你现在最依赖 DOM 测量的组件做 A/B 对比:
- 旧方案:
getBoundingClientRect()或offsetHeight - 新方案:同一字体和行高下,预先
prepare(),宽度变化时只layout()
对比下面这些行为:
- resize 时是否更稳
- 长列表滚动时是否更顺
- 多语言切换时布局抖动是否减少
- 是否更容易做高度预测和虚拟化
常见报错和解决建议
报错 1:测出来的高度和真实页面不一致
原因
font参数和真实 CSS 不一致lineHeight传错- 在 macOS 上用了
system-ui
解决
- 用命名字体,例如
16px Inter - 确保
line-height用真实值 - 不要用模糊估算值替代真实样式参数
报错 2:textarea 内容里的空格、Tab、换行被吞掉
原因
默认模式是面向 white-space: normal 的,不会保留普通空格和硬换行。
解决
ts
const prepared = prepare(textareaValue, '16px Inter', {
whiteSpace: 'pre-wrap',
})
这个模式是 0.0.2 版本新增的,专门用于 textarea-like 文本。
报错 3:项目接上以后还是慢
原因
你把 prepare() 放进了高频路径,每次宽度变化都重新做一次。
解决
同一份文本和字体配置只做一次 prepare(),后续宽度变化只调用 layout()。
常见坑
- 把它当成完整字体渲染引擎。不是。它当前目标明确是常见网页文本布局,而不是全量字体引擎替代品。
- 用
system-ui追求精确测量。官方研究已经明确提示,在 macOS 上这并不可靠。 - 忽略默认断行前提。它当前对齐的常见文本模型包括
white-space: normal、word-break: normal、overflow-wrap: break-word、line-break: auto。 - 把 demo 当成唯一价值。项目真正最先能落地的地方,往往是虚拟列表、卡片高度预测、消息流和富文本布局。
- 只看 README,不看研究日志。这个项目真正稀缺的部分,不是 API 名字,而是它公开了哪些路试过、哪些路放弃了。
快速自检清单
- 你的
font是否和真实 CSS 完全一致 - 你的
lineHeight是否来自真实样式值 - 同一段文本是否只
prepare()了一次 - textarea 类内容是否开启了
whiteSpace: 'pre-wrap' - macOS 场景是否避免使用
system-ui - 是否先用小组件试点,而不是一次性重构整套排版逻辑
今天就能做的下一步
今天不要先想着"重构整个排版引擎"。更现实的动作是:
- 找出一个现在最依赖 DOM 测量的组件例如消息气泡、卡片摘要、按钮文案校验
- 保持视觉层不动只替换"文本高度预测"这一层
- 做一次小范围对比 看 resize、滚动、多语言切换时是否更稳
如果这一步能跑通,你再考虑把它逐步扩到消息流、虚拟列表或富文本卡片场景。
收尾
Pretext 这波真正值得关注的,不是"又一个排版库",而是它把文本测量从浏览器临时求值,拆成了可预计算、可缓存、可验证的工程模型。对做复杂前端的人来说,这个方向比一个新 API 更重要。它未必会替代所有布局方案,但已经足够成为高性能文本 UI 的一个底层能力候选。
关注 【iDao技术魔方】,获取更多全栈到AI可落地的实战干货。