📰 正文
前 React 核心团队成员、ReasonML 作者、现 Midjourney 工程师 Cheng Lou 开源了 Pretext,一个纯 TypeScript 写的文本测量和排版库。
它能在完全不碰 DOM 的情况下精确计算文本高度、行数、换行位置,让网页排版绕过浏览器里最贵的操作:布局回流(layout reflow)。
核心就一句话:能让你不用真的把文字放到网页上,就能提前知道文字会占多大空间。
先打个比方。
你装修房子,买了一个书架,想知道放在客厅那面墙能不能放得下。正常人会拿尺子量一下墙的宽度和书架的宽度,对比一下就知道了。
但浏览器不是这么干的。浏览器的做法是:把书架搬过去,塞进客厅,然后看看放不放得下。放不下?搬走,换个位置再塞一次。每次你想知道"放不放得下",它就搬一次家具。
这就是为什么网页有时候会"闪"一下,聊天列表滑着滑着会"跳"一下。
浏览器在反复搬家具。
Pretext 做的事情就是给浏览器一把尺子。
量一下就知道了,不用搬。
它用纯数学计算文字的高度和行数,不需要真的把文字放到网页上去排版。500 段文字的计算只要 0.09 毫秒,比浏览器"搬家具"快几百倍。
GitHub 一天 6000+ star,推文 780 万浏览、3.4 万点赞、3.6 万收藏。
Cheng Lou 自己的原话是"I have crawled through depths of hell to bring you this"(我从地狱深处爬出来把这东西带给你们),语气夸张,但看完 Demo 你会觉得他没吹。
Pretext 实时文字排版的效果
Web 排版 30 年的老毛病
做过前端的都知道这个痛:想知道一段文字占多高、哪里换行,你得把文字塞进 DOM,让浏览器排一遍版,再用 getBoundingClientRect 或 offsetHeight 读数值出来。
这叫布局回流,是浏览器最贵的操作之一。改文字、调宽度、加元素,浏览器可能重新算整个页面布局。
很多高级排版效果需要提前知道文字尺寸:
瀑布流要知道每个卡片多高,聊天气泡要知道最紧凑的宽度,虚拟长列表要知道每一项占多少空间,文字绕图要知道每行该放几个字。
传统做法要么粗略估算忍受跳动,要么触发大量回流拖垮性能。这个困境从 CSS 1.0 到今天,30 年了。
Pretext 的做法
思路很直接:把文本测量从 DOM 里彻底抽出来。
prepare() 用 Canvas 的 measureText 做一次性的文字测量(这一步不触发回流),把文本分段、应用换行规则、缓存每段宽度。之后调 layout(),所有计算都是纯数学运算,不再碰 DOM。
import { prepare, layout } from '@chenglou/pretext'
const prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀', '16px Inter')
const { height, lineCount } = layout(prepared, 320, 20)
// 320px 宽、20px 行高下的精确高度和行数,不碰 DOM
两行代码。性能差距很大:500 段文本的批次测试里,prepare() 总共 19ms(一次性开销),layout() 只要 0.09ms。算完 500 段文字的高度,连 0.1 毫秒都不到。
它还处理了各种语言的边缘情况:中文、日文、阿拉伯文(从右到左)、emoji、混合双向文本,全部支持。README 示例里同时出现中文、阿拉伯文和 emoji,实测都能正确处理。
不只是量高度
高度测量只是入门。Pretext 的 API 分两层,第二层让你手动控制每一行的排版,能做的事情比想象中多很多。
文字绕图: 传统 CSS 的 float 能让文字绕图,但控制力极有限。Pretext 的 layoutNextLine() 可以逐行排版,每行给不同宽度。图片旁边的行窄一点,图片下面恢复全宽,文字像杂志一样自然地绕着图片流动。
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
}
杂志和报纸排版里最基本的文字绕图,在浏览器里终于能干净地实现了。
消息气泡收缩包裹: 聊天界面里消息气泡宽度怎么定?太宽浪费空间,太窄多余换行。walkLineRanges() 能找到"保持行数不变的最窄宽度"。这个多行收缩包裹能力,Web 原生一直缺。
虚拟列表不用瞎猜高度: 做过长列表虚拟化的都知道,最头疼的就是"每一项多高"。以前要么固定高度(丑),要么先渲染再测量(慢),要么给估算值忍受跳动。现在渲染之前就精确知道高度了。
开发时校验文字溢出: 按钮上的文字会不会换行?标签会不会被截断?以前靠浏览器跑一遍才知道。Pretext 让你在构建阶段就能验证,甚至可以丢给 AI 批量检查,不需要浏览器环境。
普通用户能感受到什么
你可能觉得这是前端开发者才关心的事。但你每天都在被这个问题影响:
聊天列表不"跳"了。 微信、飞书这类应用,滑动聊天记录时偶尔会突然跳一下,因为消息高度算错了。有了 Pretext,不用渲染就能精确算出每条消息的高度,列表就稳了。
网页不"闪"了。 你打开一篇文章,内容加载出来后页面往下一跳,你正在看的东西跑到了别的位置。因为浏览器一开始不知道文字有多高。Pretext 能提前算好,预留空间,页面就不跳了。
消息气泡不浪费空间了。 你发一段长消息,气泡宽度按最长那行来,最后一行很短的时候后面全是空白。CSS 做不到"找到保持同样行数的最窄宽度"。Pretext 能算出来,气泡每个像素都不浪费。
AI 生成界面时知道文字会不会溢出。 AI 生成了一个按钮写着"立即获取限时优惠",在手机上放不放得下?以前只能渲染出来才知道。有了 Pretext,生成阶段就能算出来,超了就换措辞。
性能
在当前基准测试中:
layout() 比 prepare() 快 200 倍,因为它完全不碰浏览器。你可以在一帧(16ms)内对几千段文字重新计算布局。
语言支持
支持所有语言,包括中文、日文、韩文、阿拉伯文(RTL)、混合双向文本、emoji。Demo 里用的示例文字就是中英阿混合加 emoji 的。
支持 pre-wrap 模式(保留空格、tab、换行符),适合 textarea 场景。
安装
npm install @chenglou/pretext
MIT 协议,免费开源。
这件事的意义
Pretext 做的事情看起来很小(测量文字高度),但它打开了一扇门:让前端开发者能在 DOM 之外做布局计算。
以前你想做任何涉及"文字有多高"的计算,都绕不开 DOM 和 reflow。Pretext 把这个依赖切断了。文本测量变成了纯函数,可以在 Web Worker 里跑,可以在渲染前跑,可以在 AI 生成 UI 的时候跑。
对 AI 生成界面这个方向来说,这可能是一个关键的基础设施。AI 生成一个按钮上的文字,目前没有办法在不渲染的情况下知道文字会不会溢出。有了 Pretext,这个验证可以在生成阶段就完成。
Demo 值得看一遍
Pretext 的在线 Demo 有七个场景,每个都像"不该在浏览器里存在的东西":
手风琴折叠:展开收起高度提前算好,动画丝滑不抖
气泡消息:紧凑的多行气泡,同样的文字占更少面积
动态排版:障碍物感知的标题路由,文字连续流动
编辑引擎:实时文字重排、拉引、多栏排版,全程零 DOM 测量
富文本:内联代码、链接、标签混排,标签整体不被拆行
瀑布流:用 Pretext 预测高度代替 DOM 读取
ASCII 字符画:用比例字体做粒子驱动的 ASCII 艺术
渲染目标不限于 DOM,Canvas、SVG、WebGL 都行,服务端渲染在路线图上。
👉 Cheng Lou 原推 | GitHub | 在线 Demo