
做前端的人都知道这个场景——
写一个聊天消息列表,消息长度不一,有的长有的短。常见的做法是:先把消息渲染到 DOM 里,然后用 getBoundingClientRect() 或者 offsetHeight 去测量它有多高。
问题是:这些操作会触发浏览器强制重排(Reflow)!
一次两次还好,但如果你的列表有 100 条消息,每条消息都这样测量——浏览器可能要在同一帧里触发几十次重排,帧率直接从 60fps 掉到 10fps,用户就会感觉"卡"。
你可能会说:"那我加个 requestIdleCallback 延迟测量?"
但这治标不治本——测量结果不准确,布局还是会乱。
今天要介绍的这个库 Pretext,解决的正是这个底层问题: 在完全不触碰 DOM 的情况下,精确计算多行文本的高度和换行位置。
42.8K Stars,由前 Facebook 工程师 chenglou 打造。这个名字对前端社区来说并不陌生——他同时也是 react-virtualized、data-ui 等知名项目的作者。
演示



Pretext 是一个专注于"多行文本测量与排版"的 JavaScript/TypeScript 库,核心理念只有一句话:在真正渲染之前,预知文本的一切。
它通过 Canvas 底层 API 完成文本测量,完全绕开了 DOM,因此不会触发任何重排(Reflow)或重绘(Repaint)。测量结果可以被缓存并重复使用。
| 关键指标 | 数值 |
|---|---|
| Stars | 42.8K |
| Forks | 2.3K |
| 语言 | TypeScript / JavaScript |
| 渲染目标 | DOM / Canvas / SVG / WebGL / 服务端 |
| 多语言支持 | CJK(中日韩)/ 阿拉伯语(RTL)/ 各类 Unicode |
| 许可证 | MIT |
一句话定位:不需要 DOM,就能知道文本长什么样的文本测量引擎。
传统方案的问题:
javascript// ❌ 每次都触发 DOM 测量 + 重排
const el = document.createElement('div');
el.textContent = '这是一条很长的消息...';
document.body.appendChild(el);
const height = el.offsetHeight; // 👈 强制重排!
Pretext 的做法:
javascriptimport { prepare, layout } from '@chenglou/pretext';
// 第1步:一次性分析文本(不触碰 DOM!)
const prepared = prepare('这是一条很长的消息,可能跨多行,需要精确知道每行的宽度...');
// 第2步:给定任意宽度,直接算出高度和换行位置
const result = layout(prepared, { width: 320 });
console.log(result.height); // 👈 精确的高度数值,不触发任何重排!
// 如果宽度变了,只需要重新调用 layout()
// prepared 数据可以被缓存复用
const result2 = layout(prepared, { width: 280 });
整个过程中,DOM 从未被触碰,没有 appendChild,没有 getBoundingClientRect,没有 offsetHeight。
测量结果可以直接用于:
当你需要在 Canvas 或 SVG 上渲染文本时,最大的难点在于:Canvas 没有内置的"自动换行"功能。
传统做法是:每行文字先渲染到临时 Canvas,测量宽度,超过边界就换行。效率低,而且很难处理混合样式(粗体、链接、代码片段)。
Pretext 提供了精确到字符的换行控制:
javascriptimport { prepareWithSegments, layoutWithLines } from '@chenglou/pretext';
const segments = [
{ text: '@深港数码', style: { color: 'blue' } }, // 蓝色用户名
{ text: ' 推荐大家看看 ', style: {} }, // 普通文字
{ text: 'Pretext', style: { bold: true } }, // 加粗关键词
{ text: ' 这个库,真的很快!', style: {} },
];
const prepared = prepareWithSegments(segments);
const { lines } = layoutWithLines(prepared, { width: 280 });
lines.forEach((line, i) => {
console.log(`第${i + 1}行: "${line.text}" 宽度=${line.width}`);
// 👆 精确的每行文本内容,可以完美渲染到 Canvas/SVG 上
});
返回的 lines 数组包含了每行的:
text)width)startIndex / endIndex)这意味着:任何复杂的富文本样式组合,你都能精确渲染到 Canvas 上,而且永远不需要用 DOM 打补丁。
javascriptimport { walkLineRanges, measureLineStats } from '@chenglou/pretext';
const prepared = prepare(text);
// 获取光标在第5行时的位置信息
const lineRanges = walkLineRanges(prepared, { width: 300 });
const charIndex = lineRanges[4].startIndex; // 第5行的起始字符索引
// 获取精确的行统计数据
const stats = measureLineStats(prelineStats, { width: 300 });
console.log(`第${5}行包含 ${stats.lineCount} 个字符`);
这套 API 可以用于:
javascript// Node.js 环境下完全可用
import { prepare, layout } from '@chenglou/pretext';
const serverResult = layout(
prepare('服务端渲染时也能精确知道文本高度'),
{ width: 375 }
);
服务端预先计算好文本高度,前端 hydration 后直接使用,完全消除了布局抖动(Layout Shift)。这对于 SEO 和 Core Web Vitals(CLS 指标)有直接帮助。
Pretext 内置了完善的 Unicode 处理逻辑,支持:
| 语言类型 | 示例 | 处理 |
|---|---|---|
| 中文/日文/韩文(CJK) | 你好世界 | 等宽字符处理,无需空格分词 |
| 阿拉伯语(RTL) | مرحبا | 右到左双向文本自动翻转 |
| 混合内容 | Hello 你好 مرحبا | 智能混合排版 |
对于东亚文字来说,最大的优势在于:不需要空格就能分词,传统 JS 文本库在这个场景下几乎无法工作,Pretext 处理得很好。
项目提供了丰富的交互 Demo(访问 chenglou.me/pretext 查看):
| Demo | 演示内容 | 核心价值 |
|---|---|---|
| Accordion | 手风琴展开/收起动画 | 展开高度预知,动画丝滑 |
| Bubbles | 聊天消息气泡,固定行数 | 多行消息气泡宽度一致 |
| Dynamic Layout | 障碍物感知的文字路径 | 文字绕开障碍物流动 |
| Variable Typographic | 等宽 vs 比例字体的精确对比 | 展示 Pretext 的字符级精度 |
| Editorial Engine | 杂志级多栏排版 | 实时文本回流、侧边栏、图片穿插 |
| Justification Comparison | CSS对齐 / 贪心断词 / Knuth-Plass 算法对比 | 三种断行算法直观对比 |
| Rich Text | 带代码、链接、Chips 的富文本 | 混合样式精确换行 |
| Markdown Chat | 虚拟化聊天列表 | 虚拟列表 + Pretext 高度预计算 |
| Masonry | 瀑布流卡片 | 无 DOM 测量的瀑布流高度预测 |
Pretext 的技术路径非常巧妙:
measureText() API 获取字符级精确宽度(Canvas API 不触发重排)这意味着:测量一次,后续无论调用多少次 layout(),都是纯算术运算,时间复杂度 O(1)。
| 操作 | DOM 测量 | Pretext |
|---|---|---|
| 单次测量 100 字符 | ~5ms | ~0.1ms |
| 1000 条消息列表初始化 | 触发多次 Reflow | 无 DOM 操作 |
| 宽度变化后重新计算 | 需重新渲染 DOM | 直接调用 layout(),毫秒级 |
| 服务端计算 | 无法使用 | ✅ 完全支持 |
具体数据因浏览器和硬件差异而不同,但 Pretext 在所有场景下都显著优于 DOM 测量方案。
chenglou 在项目中贯彻了一个非常清晰的设计哲学:只做测量,不做渲染。
Pretext 不试图成为一个完整的文本渲染引擎。它专注于:
渲染部分完全交给用户——你可以渲染到 Canvas、SVG、WebGL,或者完全不用它,自己处理 DOM。
这种"专注单一职责"的设计让 Pretext 体积小、API 清晰、性能极高。
| 场景 | 使用方式 |
|---|---|
| 虚拟列表/虚拟滚动 | 预计算列表项高度,无需先渲染再测量 |
| 聊天/消息界面 | 多行消息气泡宽度精确一致,无跳动 |
| 瀑布流/Masonry 布局 | 预知卡片高度,排列组合最优化 |
| Canvas/SVG 文本渲染 | 精确换行,支持混合样式 |
| 服务端文本预计算 | 消除 CLS,提升 CWV 分数 |
| 富文本编辑器 | 精确光标定位和行号计算 |
| 多语言国际化应用 | CJK / RTL 文本自动处理 |
bashnpm install @chenglou/pretext
# 或
pnpm add @chenglou/pretext
javascriptimport { prepare, layout } from '@chenglou/pretext';
// 准备文本数据(一次性操作)
const prepared = prepare('这是要测量的文本内容');
// 在任意宽度下计算高度
const { height, lines } = layout(prepared, { width: 320 });
console.log(height); // 精确的像素高度
console.log(lines); // 每行的详细信息数组
javascriptimport { prepareWithSegments, layoutWithLines } from '@chenglou/pretext';
const segments = [
{ text: 'Hello ', style: { color: 'black' } },
{ text: 'Pretext', style: { bold: true, color: '#E8655A' } },
];
const { lines } = layoutWithLines(prepareWithSegments(segments), { width: 200 });
// 渲染到 Canvas
lines.forEach((line, i) => {
ctx.fillText(line.text, 0, 20 + i * 24);
});
bashgit clone https://github.com/chenglou/pretext.git
cd pretext
bun install
bun start
# 打开 http://localhost:3000
Pretext 是一个看起来"很小",但解决了一个"很底层"问题的库。
它的价值不在于功能多花哨,而在于:把文本测量这件事,做到了极致。
42.8K Stars 说明了一切——这个库被大量项目依赖,涵盖了从聊天应用到高端编辑器,从虚拟列表到杂志排版的各种场景。
如果你正在做:
把 Pretext 加入你的技术栈,它会让这些问题变得异常简单。
项目链接:
npm install @chenglou/pretext
本文作者:KK
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!