Web 做了三十年都没做到的事,被一个 15KB 的库解决了
上周,Cheng Lou 发了一个库叫 pretext。
三天,13000 个 star。
你可能会说,开源项目爆火不稀奇。但这个库做的事情,让我看了之后在椅子上坐了五分钟没动。
它做的事情是:告诉你一段文字渲染出来有多高。不碰 DOM。
一行代码的事
在 iOS 上,你想知道一段文字渲染后多高:
label.sizeThatFits(CGSize(width: 300, height: .greatestFiniteMagnitude))
一行。没有副作用。不会让整个页面重新算一遍布局。
Android 有 StaticLayout。Flutter 有 TextPainter。Jetpack Compose 有 TextMeasurer。
每一个主流原生平台,都把「量文字」当成一个独立的、可以随时调用的操作。
Web 呢?
Web 的唯一办法是:把文字塞进 DOM → 强迫浏览器重新计算整棵渲染树的布局 → 读出高度 → 把元素删掉。
这个操作叫 layout reflow。它是浏览器里最贵的操作之一。
更要命的是,CSS 的布局是全局耦合的。你改了一个元素,整页都得重新算。
三十年的技术债
为什么会这样?
因为 CSS 是 1994 年设计的。那时候 Web 是学术论文交换系统。没人想到它会变成应用平台。在纯文档的场景下,「独立查询布局结果」这个需求根本不存在,所以没人设计这个接口。
1994 年完全合理的决定。三十年后,变成了天文数字的技术债。
2012 年 Facebook 用 HTML5 做移动端,崩了。一个核心原因就是 DOM 的全局 reflow 机制扛不住 News Feed 的性能要求。Facebook 花了九个月从头写原生 iOS 客户端。启动时间从十秒降到四秒。
后来他们造了 React Native 的 Yoga 布局引擎——本质上就是在绕过 CSS 缺失的那一层抽象。
W3C 知道这个问题。CSS Houdini 的 Font Metrics API 就是为了解决它。但到 2026 年了,没有一个浏览器实现了。
Pretext 怎么做的
Pretext 找到了一个后门:canvas.measureText()。
Canvas 的文字渲染和 DOM 共用同一个字体引擎,但 Canvas 完全在布局树之外运行。通过 Canvas 量文字,永远不会触发 reflow。
两个阶段:
冷路径(一次性): prepare(text, font) —— 用 Intl.Segmenter 做语言感知的分词(处理中日韩文、双向文字、emoji、软连字符),通过 Canvas 测量每个片段的宽度,缓存起来。500 段文字大约 19ms。
热路径(便宜): layout(prepared, maxWidth, lineHeight) —— 纯算术,在缓存的宽度数据上模拟浏览器的换行算法。返回 { height, lineCount }。完全不碰 DOM。500 段文字 0.09ms。
准确度?Cheng Lou 把《了不起的盖茨比》全文在 Chrome、Safari、Firefox 上渲染,逐像素和 Pretext 的预测对比,反复迭代直到匹配。测试语料扩展到了泰语、中文、韩语、日语、阿拉伯语。
不只是快
性能是最明显的收益。但更深的影响是 Pretext 解锁了什么:
真正的虚拟列表。 现在的虚拟列表要么猜高度,要么先渲染再测量(导致可见的跳动)。Pretext 让你在元素进入视口之前就知道精确高度。零布局偏移。
多行收缩包裹。 CSS 没有办法说「让这个容器的宽度刚好等于最宽那行的宽度」。聊天气泡、标签、徽章——全都比需要的宽。Pretext 的 walkLineRanges() 可以二分搜索出数学上最紧的宽度。这是一个 Web 字面意义上无法表达的布局原语。
文字绕障碍物排版。 layoutNextLine() 让每一行有不同的最大宽度。文字绕着图片、形状、移动物体排版——杂志式的排版 CSS 因为复杂度放弃了——现在在用户态就能做。
更深的一课
我的朋友 yage 写了一篇非常好的分析,从架构设计的角度看这件事:
好的抽象让你选择在哪一层工作。坏的抽象把所有层粘在一起,不给你选择。
CSS 选择隐藏中间布局状态。1994 年看起来干净优雅。三十年后,利息高得吓人。
yage 画了一个很尖锐的类比:我们现在给 AI 系统设计 API 也在犯同样的错误。面向人类的 API 捕获底层错误,抛出一个干净的「操作失败,请重试」。对人类来说这是保护认知带宽。但当使用者变成一个需要「尝试 → 反馈 → 修正」循环的 AI Agent 时,这种保护性抽象就变成了障碍。
AI 需要观察中间状态。不是让你把它封在黑盒子里。
Pretext 的架构——在现有系统旁边开一个独立的观察窗口,而不是替换它——就是正确的模式。
去看 Demo
如果你还没看过 pretext 的 demo,现在就去。
十万个不同高度的文本项做瀑布流排版,120fps。文字实时绕着移动障碍物排版。聊天气泡完美收缩包裹——每个气泡的宽度精确到像素。
社区 demo 也很疯狂。pretext-explosive 把每个字符变成物理粒子,点击爆炸。illustrated-manuscript 让中世纪手稿的文字绕着动画龙排版。
这些不是技术炫技。它们是以前在 Web 上不可能——或者至少贵得离谱——的 UI 模式。
15KB。零依赖。一个人用 AI 辅助编码在几周内完成。
有时候最重要的基础设施,不是最复杂的。而是补上了最该存在、却一直不存在的那一层。
Pretext:github.com/chenglou/pretext。yage 的架构分析:yage.ai/web-layout-tradeoff.html。