Pretext 完全解析:前端文本排版引擎如何用纯算术干掉 DOM 重排
前端圈最近被一个名为 Pretext 的项目点燃了。它由前 React 核心成员、现 Midjourney 前端工程师 Cheng Lou 开发,一开源就引爆社区,GitHub 星标迅速破万,全网超过 1600 万人围观。

这不仅仅是一个新库,它代表了一次前端文本能力的“重启”。它彻底绕过了浏览器统治了30年的DOM渲染机制,用纯算术的方式实现了极速、精准的文本布局。

🚫 它解决了什么痛点?

在网页上,但凡涉及动态文本——比如聊天消息、文章卡片、虚拟列表、自动换行输入框——你都得问浏览器:“这段文字在这个宽度的容器里,到底会占多高?”

过去唯一的办法是:把文本放进一个隐藏的DOM元素,等浏览器排版完,再用 getBoundingClientRect()offsetHeight 去读取结果。这个操作叫做 Reflow(重排),它会强制浏览器重新计算整个页面的流式布局。尤其是在长列表、频繁窗口缩放、或AI内容实时流式输出的场景下,性能会直接“雪崩”。

很多框架为了性能,只能采用批量测量、缓存估算值、妥协精度的做法,结果就是滚动卡顿、布局偏移(CLS)、虚拟化高度猜不准。Pretext 把这一切全部绕过去了:它不碰DOM,不触发重排,用纯算术的方式,在微秒级别内给出答案,并且结果与浏览器原生渲染像素级一致。

⚙️ 核心原理:“冷路径 + 热路径”

Pretext 的逻辑核心分为两步,它将“昂贵”的操作与“高频”的操作彻底分离:

1. prepare() —— 冷路径(一次性重活)

这一步相对“昂贵”,但只运行一次。它会:

  • 将输入文本按语义拆分成最小单元(词、字符、emoji),使用 Intl.Segmenter 正确处理组合字符。
  • 利用 CanvasmeasureText() API(此API不会触发DOM重排)作为“真理源头”,精确测量每个单元的宽度。
  • 将所有测量结果缓存起来,并规范化处理空白字符、换行规则和双向文本(bidi)。

当文本或字体不变时,这一步只执行一次。

2. layout() —— 热路径(纯算术运算)

这是Pretext真正神奇的地方。它接收缓存的句柄、目标宽度和行高后,不依赖任何浏览器API,直接进行纯加减乘除的算术运算,在内存中模拟出完整的换行结果,并瞬间计算出最终高度和每行的精确位置。

这一步的速度是微秒级的。在官方基准测试中,layout() 仅需 0.09 毫秒,而传统DOM测量方式需要 43.50 毫秒,在Safari上甚至快了超过 1200倍

📦 核心API拆解与使用

Pretext 提供了两套API,一套简单易用,另一套则提供了对布局的完全控制。

简单API:快速获取高度

绝大多数业务场景,用这套就够了。

1
2
3
4
5
6
7
8
9
10
11
import { prepare, layout } from '@chenglou/pretext'

// 1. 准备文本和字体(字体声明必须与CSS完全一致)
const prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀', '16px Inter')

// 2. 计算布局:宽度300px,行高24px
const { height, lineCount } = layout(prepared, 300, 24)

// 对于需要保留空格和换行的文本(如 textarea)
const preparedWithPreWrap = prepare(textareaValue, '16px Inter', { whiteSpace: 'pre-wrap' })

性能数据:prepare 对500条混合文本批次约需19ms,而layout同一批次仅需0.09ms。实际项目中,你可以在组件挂载时 prepare 一次,在窗口 resize 或容器宽度变化时只跑 layout,帧率直接起飞。

高级API:完全控制每一行

面向Canvas、SVG、WebGL,甚至未来的服务端渲染场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { prepareWithSegments, layoutWithLines, layoutNextLine } from '@chenglou/pretext'

// 获取带段信息的结构
const prepared = prepareWithSegments(text, '18px "Helvetica Neue"')

// 1. 固定宽度,返回所有行信息
const { lines } = layoutWithLines(prepared, 320, 26)
for (const line of lines) {
ctx.fillText(line.text, 0, y)
y += 26
}

// 2. 迭代器模式,处理变宽场景(如文字绕图、杂志多栏)
let cursor = { segmentIndex: 0, graphemeIndex: 0 }
let y = 0
while (true) {
// 根据当前y坐标动态决定行宽
const width = (y < imageBottom) ? columnWidth - imageWidth : columnWidth
const line = layoutNextLine(prepared, cursor, width)
if (!line) break
ctx.fillText(line.text, 0, y)
cursor = line.end
y += lineHeight
}

💡 它能做什么?

Pretext 让以前CSS很难实现甚至无法实现的UI效果变得轻而易举:

  • 文字绕图与任意形状流动:文本不再局限于矩形,可以像水流一样环绕任何图形或图片边缘。
  • 高性能虚拟列表:可以精确预测列表项高度,无需挂载真实DOM去测量,实现百万级数据的流畅滚动,彻底消除布局偏移(CLS)。
  • 自适应聊天气泡(Shrinkwrap):实现多行文本的“收缩包裹”效果,让气泡完美贴合文本内容,这在Web上缺失了整整30年。
  • 动态杂志排版的列布局:轻松实现响应式的多栏文字,且在不同宽度下过渡丝滑。
  • Canvas/SVG/WebGL渲染一致性:以前DOM文字和Canvas文字永远对不齐,现在同一份数据,两边渲染结果可以做到像素级一致。

最直观的证明就是社区开发者们用Pretext实现的疯狂脑洞:在文字流里跑《超级马里奥》游戏、文字跟随重力感应、实时演示胡克定律的物理小球……这些以前需要复杂技巧才能实现的效果,现在都变得简单了许多。

🧩 总结:不是取代CSS,而是拓展边界

很多人会问:这不就是把CSS的活抢了吗?比如 CSS Shapes

不是的。CSS依然负责最终的绘制(如果你用DOM渲染),Pretext只管测量和布局决策。你依然可以用Tailwind、Flex、Grid来搭框架,但关键的文本高度现在变得可控、可预测了。CSS的文本模块从来就不是为“精确可编程”设计的,它是声明式、黑盒的。而Pretext把控制权交还给JavaScript,让你能做到CSS做不到的事:变宽迭代、精确shrinkwrap、跨平台一致的布局。

两者结合,才是未来。

当然,它也不是万能药。目前不直接支持文本选择(selection)样式,但通过覆盖透明Canvas可以曲线救国。复杂排版规则如两端对齐(full justification)的高级微调也还在社区扩展中。但作者开放态度明显,未来路线图包括服务端渲染(SSR)、更多语言支持等。

🚀 结论

Pretext 不是一个普通的库,而是一次前端文本能力的底层革新。它证明了:把浏览器字体引擎的真相前置缓存 + 纯JS布局,就能彻底干掉DOM重排这个老大难问题。

对普通开发者,它让你写出更丝滑的列表、更稳的编辑器;对设计师,它打开了杂志级响应式排版的大门;对AI时代,它让文本从“渲染后才知道”变成“生成前就知道”,让智能界面真正智能起来。

文本,不再是瓶颈,而是创意的起点。


参考资料


> 本文同步发表在微信公众号「搬砖奶爸」,欢迎关注。
> https://mp.weixin.qq.com/s/fHS97pgynO9p2EKe0Y-Z1Q