阅读视图

发现新文章,点击刷新页面。

Pretext:一个绕过 DOM 的纯 JavaScript 排版引擎

本文首发 猩猩程序员 欢迎关注

在 Web 前端开发中,"文本到底有多高"这个看似简单的问题,一直是困扰开发者的一大难题。传统做法是将文本塞入 DOM,然后通过 getBoundingClientRectoffsetHeight 等 API 来获取尺寸,但这种操作会触发浏览器的布局回流(layout reflow) ——这是浏览器中最昂贵的操作之一。现在,由 Cheng Lou 开源的 Pretext 库,用一种全新的方式彻底解决了这个问题。

Cheng Lou 是前端领域的重量级人物,曾参与 ReactJS、ReasonML、ReScript、Messenger 以及 Midjourney 等知名项目的开发。他在 2026 年 3 月底正式发布了 Pretext,一经发布便在社区引发广泛关注。

Pretext 是什么?

Pretext 是一个纯 JavaScript/TypeScript 编写的多行文本测量与排版库。它的核心理念是:完全绕过 DOM,用纯算术运算完成文本高度计算与行排版。 它借助浏览器自身的字体引擎(通过 Canvas 的 measureText)作为真实度量来源,实现了自己的文本测量逻辑。换句话说,Pretext 做了一次前期准备(prepare),之后的所有布局计算都是极其廉价的纯数学运算,无需再次触碰 DOM。

更令人印象深刻的是,Pretext 支持几乎所有你能想到的语言和文字系统,包括 emoji 和混合双向文本(Bidi),并且针对不同浏览器的排版差异进行了专门处理。

为什么需要 Pretext?

Web 开发中,文本高度的获取是许多高级 UI 功能的基石,但传统方法的代价太高。Pretext 的出现为以下场景提供了全新的解决方案。

虚拟列表与遮挡剔除(Virtualization/Occlusion)。 在构建长列表或无限滚动界面时,开发者需要知道每个条目的高度以正确计算滚动位置。过去,这通常依赖粗略的估算或缓存值。Pretext 能以极低的成本精确计算文本高度,让虚拟列表的实现变得可靠且高效。

自定义布局引擎。 想在 JavaScript 中实现瀑布流(Masonry)布局、类 Flexbox 布局,或是对布局值进行微调而不依赖 CSS hack?Pretext 提供的精确文本尺寸信息,正是这些自定义布局所需的关键输入。

开发时验证。 特别是在 AI 辅助生成 UI 的今天,开发者可以在不打开浏览器的情况下验证按钮上的文案是否会溢出到下一行。

消除布局偏移(Layout Shift)。 当新文本加载后需要重新锚定滚动位置时,Pretext 可以提前计算高度,从而避免恼人的页面跳动。

核心 API 设计

Pretext 的 API 设计非常精巧,分为两个主要使用场景。

场景一:快速测量段落高度

这是最常见的使用方式。只需两步:先用 prepare() 做一次性的文本分析和度量,然后用 layout() 进行纯算术计算。

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

const prepared = prepare('AGI .   ', '16px Inter')
const { height, lineCount } = layout(prepared, textWidth, 20)

prepare() 负责繁重的前期工作:规范化空白字符、分词分段、应用断行规则,以及通过 Canvas 度量各段的宽度,最终返回一个不透明的句柄。layout() 则是高频热路径——只做纯数学运算,无任何 DOM 交互。

在官方的基准测试中,对 500 段文本的批处理中,prepare() 耗时约 19ms,而 layout() 仅需约 0.09ms。这意味着在需要反复重排布局的场景(如窗口缩放、响应式调整)中,性能优势极为显著。

场景二:手动逐行排版

对于更高级的渲染需求,Pretext 提供了一套丰富的底层 API,支持渲染到 Canvas、SVG、WebGL 等非 DOM 目标。

layoutWithLines() 可以在固定宽度下返回所有行的详细信息:

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

const prepared = prepareWithSegments('AGI .   ', '18px "Helvetica Neue"')
const { lines } = layoutWithLines(prepared, 320, 26)
for (let i = 0; i < lines.length; i++) ctx.fillText(lines[i].text, 0, i * 26)

walkLineRanges() 是一个低级 API,可以在不构建文本字符串的情况下遍历行宽和游标信息,非常适合做投机性布局探索——例如通过二分搜索找到一个"恰到好处"的容器宽度,实现 CSS 原生无法做到的多行文本"收缩包裹(shrink-wrap)"效果。

let maxW = 0
walkLineRanges(prepared, 320, line => { if (line.width > maxW) maxW = line.width })
// maxW 就是最宽行的宽度——也就是能容纳文本的最窄容器宽度!

layoutNextLine() 则提供了迭代器风格的逐行排版能力,允许每一行使用不同的宽度。这对于文本环绕浮动元素的场景尤其有用:

let cursor = { segmentIndex: 0, graphemeIndex: 0 }
let y = 0

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
}

pre-wrap 模式

Pretext 还支持 pre-wrap 白空间模式,适用于类似 <textarea> 的场景,保留普通空格、\t 制表符和 \n 硬换行符。只需在 prepare()prepareWithSegments() 中传入 { whiteSpace: 'pre-wrap' } 选项即可。制表符遵循浏览器默认的 tab-size: 8 行为。

当前限制

Pretext 目前并不试图成为一个完整的字体渲染引擎,而是聚焦于最常见的文本排版配置:white-space: normal(或 pre-wrap)、word-break: normaloverflow-wrap: break-wordline-break: auto。在 macOS 上,使用 system-ui 字体可能影响 layout() 的精度,建议使用具名字体。此外,由于默认包含 overflow-wrap: break-word,在极窄的容器中,单词可能会在字素(grapheme)边界处被断开。

设计哲学与技术渊源

Pretext 的核心架构可以追溯到 Sebastian Markbage 在上一个十年创建的 text-layout 项目。其中利用 Canvas measureText 进行字形度量、借鉴 pdf.js 的双向文本处理、以及流式断行算法等设计思想,都被 Pretext 继承和进一步完善。

值得注意的是,Pretext 的迭代方法被描述为"非常 AI 友好"——这意味着开发者可以很方便地让 AI 工具利用 Pretext 的 API 来构建和验证复杂的文本布局。

对 Web 开发的意义

Pretext 的发布标志着 Web 文本排版领域的一个重要里程碑。长期以来,Web 开发者不得不将布局决策完全交给浏览器的 CSS 引擎,而 Pretext 打开了一扇新的大门——让 JavaScript 拥有了独立于 DOM 的、精确的文本排版能力。

这对于构建下一代 Web UI 具有深远影响。无论是 AI 驱动的动态界面生成、高性能虚拟滚动列表、跨渲染目标(Canvas/SVG/WebGL/服务端)的统一文本排版,还是 CSS 原生无法实现的"收缩包裹"与"平衡文本"布局,Pretext 都提供了坚实的基础。

快速上手

安装非常简单:

npm install @chenglou/pretext

想要体验更多 Demo,可以访问 chenglou.me/pretext 或克隆仓库后运行 bun install && bun start 在本地查看。

总结下吧

Pretext 用一种优雅而务实的方式,解决了 Web 开发中一个由来已久的痛点。它不是要取代浏览器的排版引擎,而是为开发者提供了一个轻量、快速、精确的补充工具,让文本测量不再是性能瓶颈,让布局创新不再受限于 DOM 的束缚。对于任何需要精确控制文本排版的前端项目来说,Pretext 都值得关注和尝试。

本文首发 猩猩程序员 欢迎关注

❌