蓝戒博客

  • 首页
  • 研发说
  • 架构论
  • 效能录
  • AI谈
  • 随笔集
智构苍穹
融合 AI、架构与工程实践,沉淀方法论,构建可持续的技术价值。
  1. 首页
  2. 研发说
  3. 正文

前端文本测量的三十年之痛,终于被一个 15KB 的库终结了

2026年4月10日 6点热度 0人点赞 0条评论

大家好,我是蓝戒,本篇我们来聊聊 “Pretext 这个开源项目”。

你有没有做过聊天列表?虚拟滚动?AI 流式输出界面?

如果有,你一定踩过这个坑:我需要知道一段文字到底有多高,但浏览器告诉你——先渲染,再测量。

于是你 getBoundingClientRect(),于是你 offsetHeight,于是浏览器触发 reflow,于是页面卡了,用户骂了,你加班了。

这条死路,前端走了三十年。直到 2026 年 3 月,一个叫 Pretext 的项目横空出世,GitHub 四天破 25000 Star,推特官宣帖 1900 万阅读量——它宣告:文本测量不需要 DOM,纯算术就够了。


痛点:那个你习以为常的"隐形杀手"

让我们把场景拉回到日常开发。

你正在做一个消息流页面,每条消息长短不一。为了实现虚拟滚动,你必须提前知道每条消息的高度。你的第一反应是什么?

// 传统方案:先塞进 DOM,再量尺寸
const el = document.createElement('div');
el.textContent = message;
container.appendChild(el);
const height = el.getBoundingClientRect().height;

看起来没问题?但当消息量从 10 条变成 1000 条,事情就不对了。

getBoundingClientRect() 和 offsetHeight 会触发浏览器的布局重排(Reflow)。 这不是什么"微优化"可以忽略的开销——一次 reflow 可能消耗 30ms,而浏览器一帧只有 16.6ms。1000 条消息测量下来,你直接丢掉几十帧,滚动一卡一卡,体验灾难。

更可怕的是,这不是个例。以下场景全部被同一个问题卡住:

  • 虚拟列表:不确定每项高度,只能先渲染再修正,滚动时闪烁跳动
  • AI 流式输出:每来一个 token 就要重算布局,主线程被 reflow 吃满
  • 聊天气泡:想让气泡紧贴文字,但 CSS max-width 留白过多
  • 文字绕图:想每行给不同宽度,CSS 做不到
  • Canvas/SVG 排版:文字永远和 DOM 对不齐

这就是前端开发者忍受了三十年的默认设定:想量文字尺寸?先把文字画出来。

所有人都接受了这条规则。直到 Cheng Lou 说——不。


谁是 Cheng Lou?

你可能不熟悉这个名字,但你一定用过他的作品:

  • React 核心团队成员,在 Facebook 与 React 创始人并肩工作
  • react-motion 作者,21000+ Star,把 UI 动画从定时曲线带入物理弹簧时代
  • ReasonML / ReScript 主导者,把 ML 级别的类型安全带到了 Web

他现在在 Midjourney 工作,5 人工程团队服务数百万用户。Midjourney 的 UI 实时流式输出 AI 生成内容,文本测量是命门——而 reflow 就是那把悬在头顶的刀。

Cheng Lou 一贯的风格是:找到所有人习以为常的约束,然后从根本上否定它。 动画不该用定时曲线,类型不该渐进式,文本测量不该依赖 DOM。


解法:Pretext 的两阶段架构

Pretext 的核心思想极其简洁——把"测量"和"布局"拆开,重活只做一次,后续全是纯算术。

阶段一:prepare() —— 一次性预处理

import { prepare, layout } from '@chenglou/pretext'

const prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀', '16px Inter')

这一步做了什么?

  1. 规范化空白字符(处理空格、tab、换行)
  2. 用 Intl.Segmenter 分词(Unicode 级别的词边界识别,支持中文、阿拉伯语、emoji 一切语言)
  3. 通过 Canvas measureText() 测量每个片段的宽度(利用浏览器自身的字体引擎,确保精度)
  4. 缓存所有结果,返回一个不透明句柄

这一步相对较重,但只执行一次。官方基准:500 段混合文本,prepare() 大约 19ms。

阶段二:layout() —— 纯算术布局

const { height, lineCount } = layout(prepared, 300, 20)
// 宽度 300px,行高 20px → 直接得到高度和行数

这一步完全不碰 DOM。它拿到缓存的宽度数据,跟踪一个累加值,超过容器宽度就换行,最后乘以行高得到总高度。纯算术,零副作用。

同样的 500 段文本,layout() 只要 0.09ms。

换算一下:比传统 DOM 测量快 300~600 倍。 1000 次连续测量的对比:

方法耗时(1000 次测量)触发 Reflow速度比
DOM (getBoundingClientRect)~94ms1000 次1x
Canvas (measureText)~5ms0~19x
Pretext (prepare + layout)~0.05ms0~500x
Pretext (layout only)~0.02ms0~4700x

数据来源:Pretext Benchmark Emelia.io

这不是渐进式优化,这是范式切换。


关键洞察:prepared 句柄可复用

这是 Pretext 最精妙的设计。一次 prepare(),无数 layout()。

const prepared = prepare(longText, '16px Inter')

// 移动端
const mobile = layout(prepared, 375, 20)

// 平板
const tablet = layout(prepared, 768, 22)

// 桌面
const desktop = layout(prepared, 1200, 24)

同一份文本,三种宽度,三次纯算术调用,零 DOM 操作。

这意味着:窗口 resize 时不用重新测量,侧边栏拖拽时不用重新测量,响应式布局切换时不用重新测量。 只需要重新跑一遍 layout(),0.09ms 搞定。


高级玩法:逐行控制,解锁 CSS 做不到的事

如果你只需要知道"这段文字多高",上面的 API 就够了。但 Pretext 还提供了更强大的能力——逐行布局控制。

文字绕图

import { prepareWithSegments, layoutNextLineRange, materializeLineRange } from '@chenglou/pretext'

const prepared = prepareWithSegments(article, BODY_FONT)
let cursor = { segmentIndex: 0, graphemeIndex: 0 }
let y = 0

while (true) {
  // 图片旁边的行窄,图片下方的行宽
  const width = y < image.bottom ? columnWidth - image.width : columnWidth
  const range = layoutNextLineRange(prepared, cursor, width)
  if (range === null) break

  const line = materializeLineRange(prepared, range)
  ctx.fillText(line.text, 0, y)
  cursor = range.end
  y += 26
}

每一行可以有不同的可用宽度。 这意味着文字可以围绕任意形状流动——矩形、圆形、甚至一条龙。社区已经有人做了龙绕文字的 60fps 实时 Demo,80 个关节的龙在文本间穿行,每一帧都在重算布局,丝滑无比。

聊天气泡 Shrinkwrap

import { walkLineRanges, layoutWithLines } from '@chenglou/pretext'

// 二分搜索找到最紧凑的气泡宽度
const prepared = prepareWithSegments(message, '16px Inter')
let maxLineWidth = 0
walkLineRanges(prepared, 320, line => {
  if (line.width > maxLineWidth) maxLineWidth = line.width
})
// maxLineWidth 就是最紧凑的容器宽度,气泡不再留白

这个"多行 Shrinkwrap"能力,CSS 至今做不到。你没法让一个多行文本容器自动缩到最紧凑的宽度而不改变行数。Pretext 几行代码就搞定了。

富文本行内流

Pretext 还提供了 @chenglou/pretext/rich-inline 子模块,处理代码标记、@提及、标签芯片等混合排版的场景:

import { prepareRichInline, walkRichInlineLineRanges } from '@chenglou/pretext/rich-inline'

const prepared = prepareRichInline([
  { text: 'Ship ', font: '500 17px Inter' },
  { text: '@maya', font: '700 12px Inter', break: 'never', extraWidth: 22 },
  { text: "'s rich-note", font: '500 17px Inter' },
])

walkRichInlineLineRanges(prepared, 320, range => {
  const line = materializeRichInlineLineRange(prepared, range)
  // 每个片段保留了原始 item 索引、文本切片、间隙和游标
})

break: 'never' 保证 @提及不被换行打断,extraWidth 给标签芯片留出内边距和边框的空间。


它能用在哪儿?真实场景逐一击破

1. 虚拟列表 / 瀑布流

以前做变高虚拟列表,要么估算高度导致滚动跳动,要么先渲染再测量导致二次刷新。现在用 Pretext,所有 item 高度在渲染前就已知,遮挡剔除变成简单的线性扫描,Masonry 瀑布流也能稳定 120fps。

2. AI 流式输出

ChatGPT 风格的流式界面,每来一个 token 就要重算高度和滚动位置。以前每次都要触发 reflow,现在只调用 layout(),0.0002ms 一次,主线程几乎无感。

3. 布局偏移(CLS)彻底消失

新文本加载时,不再需要等 DOM 渲染再调整锚点。高度已知,scroll position 直接算。Google Core Web Vitals 的 CLS 指标从此不再被文本拖累。

4. Canvas / SVG / WebGL 渲染一致性

以前 DOM 文字和 Canvas 文字永远对不齐,因为两者的测量体系不同。现在同一份 prepared 数据,两边渲染结果像素级一致。游戏 UI、数据可视化、设计工具直接起飞。

5. 服务端预排版

Pretext 不依赖完整 DOM,只依赖 Canvas 的 measureText()。Node.js、Deno、Bun、Cloudflare Workers 都有 Canvas 支持。你可以在服务端就算好布局,前端直接用,完全消除布局偏移。

6. AI 编程友好

Cheng Lou 特意强调 API 设计是 "AI-friendly" 的。想象一下:AI 生成 UI 组件时,不再需要猜测文本会不会溢出按钮,直接调用 Pretext 就能验证。确定性接口,可预测的输出,AI 编程助手的最佳搭档。


社区炸了:Bad Apple、3D 文字、物理引擎……创意井喷

项目开源后,社区的反应堪称疯狂。不仅有 Vercel CEO Guillermo Rauch、shadcn/ui 作者 shadcn、Inter 字体设计师 Rasmus Andersson 等大佬点赞,更有开发者们在几天内创造出令人瞠目结舌的 Demo:

  • Bad Apple 字幕动画:经典黑白 MV 的每个字都精确位移,实时流式渲染
  • 3D 高斯溅射文字环绕:WebGL 里文字围绕 3D 物体实时流动
  • 龙绕文字:80 关节的火龙在文字间穿行,每帧重算布局,60fps
  • 物理引擎文字:小球在文字行间弹跳,胡克定律实时演示
  • 面部文字映射:知名教育者 Wes Bos 把文字通过摄像头映射到人脸上
  • Knuth-Plass 排版:Cheng Lou 自己实现了 TeX/LaTeX 使用的最优断行算法,在浏览器中实时运行

tldraw——最受欢迎的协作白板工具之一——已经集成了 Pretext,实现了文本框 resize 时的零延迟文字重排。


15KB,零依赖,全语言支持

你可能觉得这么强大的库一定很重?不。

  • 约 15KB gzipped(压缩后约 5KB)
  • 零依赖
  • 框架无关:React、Vue、Svelte、Angular、Solid、原生 JS 都能用
  • 环境无关:浏览器、Node.js、Deno、Bun、Cloudflare Workers、Web Workers
  • 全语言支持:中文、日文、韩文、阿拉伯语、希伯来语、泰语、emoji、混合双向文本,通过 Intl.Segmenter 实现区域适配分词,并处理了 Chrome、Safari、Firefox 各自的换行 quirks
  • MIT 开源

准确度方面,2026 年 3 月 30 日的仪表盘快照显示:7680 / 7680 个浏览器准确性测试用例在 Chrome、Safari、Firefox 上全部通过。它用浏览器自身的字体引擎作为基准,所以精度和浏览器原生渲染完全一致。


注意事项:Pretext 不是万能的

客观来说,Pretext 目前有一些局限:

  • system-ui 字体在 macOS 上精度不安全,建议使用具名字体
  • 目前支持 white-space: normal 和 pre-wrap,word-break: normal 和 keep-all,overflow-wrap: break-word,line-break: auto
  • 不是完整的 CSS 内联格式化引擎,不支持嵌套标记树
  • prepare() 依赖 Canvas,服务端需要 Canvas polyfill
  • 还在快速迭代中(当前 npm 版本 0.0.3),API 可能变动

Cheng Lou 自己也说:Pretext 不是要取代 CSS,而是把浏览器排版引擎中"测量和换行"的能力,提取成开发者可调用的纯计算过程。 它补的是 CSS 做不到的那部分——高频、精确、无 DOM 依赖的文本测量。


五分钟上手

npm install @chenglou/pretext
# 或 pnpm add @chenglou/pretext
# 或 bun add @chenglou/pretext
import { prepare, layout } from '@chenglou/pretext'

// 第一步:准备(只做一次)
const prepared = prepare('你想测量的文本内容', '16px Inter')

// 第二步:布局(纯算术,随便调)
const { height, lineCount } = layout(prepared, 300, 20)

console.log(`高度: ${height}px, 行数: ${lineCount}`)

如果你需要逐行控制,换用 prepareWithSegments + layoutWithLines / layoutNextLineRange 即可。

在线体验:pretextjs.dev GitHub 仓库


写在最后

前端领域有一个规律:每隔几年,就有人站出来挑战一个所有人都认为是天经地义的约束。

Cheng Lou 已经做了三次。

第一次,他说动画不该用定时曲线,应该用物理弹簧——react-motion 改变了整个行业的动画思路。

第二次,他说类型不该渐进式,应该是严格的——ReasonML / ReScript 让开发者重新审视了 JavaScript 的类型安全。

第三次,他说文本测量不该依赖 DOM,应该是纯算术——Pretext 证明了三十年来所有人接受的"先渲染再测量"从来就不是必须的。

当文本测量变得几乎免费,以前不可能的事情变成了可能:实时文字绕图、零延迟虚拟滚动、像素级 Canvas 排版、服务端预计算布局……这不是一个优化库,这是一个基础设施级别的能力跃迁。

就像 Cheng Lou 自己说的:"文本布局与测量一直是解锁更有趣 UI 的最后一个、也是最大的瓶颈,尤其是在 AI 时代。解决了这个问题,我们就不再需要在 GL 落地页的炫酷和博客文章的实用性之间二选一了。"

试试 Pretext 吧。当你体验到 0.09ms 完成全部文本布局计算的那一刻,你会和我一样觉得——回不去了。

标签: chenglou DOM reflow Pretext Pretext.js 布局重排 文本布局 文本测量 虚拟滚动
最后更新:2026年4月9日

cywcd

我始终相信,技术不仅是解决问题的工具,更是推动思维进化和创造价值的方式。从研发到架构,追求极致效能;在随笔中沉淀思考,于 AI 中对话未来。

打赏 点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

cywcd

我始终相信,技术不仅是解决问题的工具,更是推动思维进化和创造价值的方式。从研发到架构,追求极致效能;在随笔中沉淀思考,于 AI 中对话未来。

最新 热点 随机
最新 热点 随机
2026 AI Agent 六大趋势:普通人如何抓住这波"数字员工"红利? 前端文本测量的三十年之痛,终于被一个 15KB 的库终结了 Google开源"Agent虚拟机"!Claude和Gemini终于能一起打工了 2个月34k Stars:一个终于不会"失忆"的AI Agent 别再把 Goose 当成“又一个 AI Agent”:它真正厉害的地方,很多人都没看懂 AI出海新风口,第一批靠“骡子快跑”搞钱的人已经出现了
效率神器 NotebookLM:构建高质量知识库的完整教程与落地最佳实践paperclip 正在把“无人运营超级个体公司”从概念,变成可执行系统免费一键部署自己的 OpenClaw,实现“养虾自由”openclaw-manager:一个把 OpenClaw 真正带进日常使用的图形化管理工具55个AI专家帮你打工:Agency-Agents让OPC(一人公司)成为现实AI + 6G:解读“十五五”科技蓝图,普通人未来十年的机会在哪里?
网站公共底文件在不同高度页面下显示位置解决方案 响应式web页面重构技术关键点 gRPC 通信协议详解与实战:高性能跨语言服务通信的基石 前端开发 TanStack 化:从“框架思维”到“能力组合”的工程演进 表单验证控件jquery.validate.js使用说明及中文API 手机端rem适配方案
最近评论
渔夫 发布于 5 个月前(11月05日) 学到了,感谢博主分享
沙拉小王子 发布于 8 年前(11月30日) 适合vue入门者学习,赞一个
沙拉小王子 发布于 8 年前(11月30日) 适合vue入门者学习,赞一个
cywcd 发布于 9 年前(04月27日) 请参考一下这篇文章http://www.jianshu.com/p/fa4460e75cd8
cywcd 发布于 9 年前(04月27日) 请参考一下这篇文章http://www.jianshu.com/p/fa4460e75cd8

COPYRIGHT © 2025 蓝戒博客_智构苍穹-专注于大前端领域技术生态. ALL RIGHTS RESERVED.

京ICP备12026697号-2