Skip to content

Pretext.js: 绕过 DOM 布局重排的高性能文本排版

作者:Cheng Lou(Midjourney 工程师、前 React 核心团队成员) 仓库:pretext 大小:仅 15KB | 许可:开源

核心问题

Web 应用中,以下场景需要计算文本高度: - 虚拟列表:动态计算每行高度以实现虚拟滚动 - 瀑布流布局:不同宽度的卡片需要精确的高度 - AI 聊天界面:气泡高度动态变化,需要锚定滚动位置

传统做法调用 getBoundingClientRect()offsetHeight,这会触发浏览器的布局重排(reflow)——渲染引擎被迫重新计算所有元素的几何信息。在复杂页面或高频操作下,帧率直接崩到 30fps 以下。

设计原理

Pretext 的核心洞察:文本测量可以完全脱离 DOM,用纯数学完成

两阶段架构

prepare() 阶段(一次性成本):
  Canvas.measureText() → 逐段文本像素宽度 → 缓存到内存

layout() 阶段(高频调用):
  缓存宽度 + 容器尺寸 → 纯数学计算 → 行数 + 最终高度

关键:两个阶段都不读 DOM,零 reflow。

为什么 Canvas.measureText() 不触发 reflow?

Canvas 是一个离屏绘制 API,measureText() 只查询字体度量信息(glyph advance width、ascent、descent),不涉及 DOM 树的布局计算。浏览器的字体缓存保证了这个操作的开销极低。

性能数据

  • 500 个文本块布局计算:0.09ms(Pretext)vs ~55ms(传统 DOM 方法)
  • 最高 600 倍性能提升
  • 在实时文本换行场景下稳定维持 120 FPS

技术实现要点

基本用法

import { prepare, layout } from 'pretext';

// 1. 准备阶段:测量文本(只需一次)
const blocks = [
  { id: 1, text: '这是一段需要测量高度的文本', font: '14px sans-serif' },
  { id: 2, text: '另一段文本,可能换行', font: '14px sans-serif' },
];
await prepare(blocks);

// 2. 布局阶段:根据容器宽度计算高度(可反复调用)
const containerWidth = 300;
const results = layout(blocks, containerWidth);
// results: [{ id: 1, height: 38, lines: 2 }, { id: 2, height: 19, lines: 1 }]

虚拟列表中的应用

// 关键:resize 时无需重新 prepare,只需重新 layout
useEffect(() => {
  const observer = new ResizeObserver(entries => {
    const width = entries[0].contentRect.width;
    const heights = layout(cachedBlocks, width);  // 0.09ms 级别
    updateVirtualList(heights);
  });
  observer.observe(containerRef.current);
  return () => observer.disconnect();
}, []);

与 CSS 方案的对比

方案 多语言支持 RTL 性能 容器自适应
CSS Grid Masonry 部分 部分 好(原生) 需要 resize 重算
CSS overflow-anchor N/A N/A 有限
JS + getBoundingClientRect 完整 完整 差(reflow) 灵活但慢
Pretext.js 完整 完整 极好 灵活且快

AI 辅助开发的典范

Pretext 的多语言支持(韩语、阿拉伯语 RTL、emoji 混排)是通过 AI 循环 完成的:

将浏览器的真实渲染结果作为基准,交给 Claude 和 Codex,在不同容器宽度下反复测量与迭代,持续数周。

这是一个"以现有软件行为为 ground truth"的 AI 开发范式——不是让 AI 凭空生成代码,而是用真实浏览器渲染结果作为验证标准,让 AI 通过试错逼近正确实现。

对移动端开发的启示

1. React Native 的类似痛点

RN 中的 onLayout 回调本质上和 DOM reflow 类似——需要等原生渲染完成才能拿到尺寸。Pretext 的思路启发了一个方向:能否在 JS 层用文本度量预计算布局,减少原生测量次数?

2. 性能优化的新层级

传统前端性能优化聚焦在: - 减少 DOM 操作(虚拟 DOM) - 减少重排重绘(批量更新、will-change) - 虚拟滚动(只渲染可见区域)

Pretext 开辟了第四个层级:绕过布局引擎本身。这对追求极致性能的移动端 Web 应用(PWA、小程序 WebView)有直接参考价值。

3. 鸿蒙场景

鸿蒙的声明式 UI 也有类似的布局测量机制。如果能在 ArkUI 中实现类似的文本预计算,可以显著提升长列表场景的帧率。

参见