纯 JS · 零 DOM 读取 · TypeScript 优先

Pretext

无需触碰 DOM

Pretext 完全通过算术运算来测量和排版多行文本 — 无 getBoundingClientRect,无回流,无抖动。首次调用即快速,后续调用瞬间完成。

Editorial EngineDrag orbs · Click to pause · Zero DOM reads
14k+
Pretext 的 GitHub stars
0
Pretext layout() 中的 DOM 读取
~2ms
Pretext 排版 1,000 个文本块
12+
Pretext 支持的书写系统
// 问题所在

Pretext.js 为何而生:每次 DOM 测量都是性能陷阱。

每当你的 JavaScript 在 DOM 节点上调用 getBoundingClientRect()offsetHeightscrollHeight 时,浏览器必须暂停脚本执行、刷新所有待处理的样式更改,并执行完整的布局计算 — 仅仅是为了回答一个关于盒子大小的问题。

这被称为强制同步回流,是浏览器能执行的最昂贵的操作之一。在紧凑的循环中 — 比如测量虚拟滚动列表中的上千个项目 — 它会导致抖动:浏览器在渲染和 JavaScript 执行之间反复切换数百次,即使是现代硬件也会被拖慢。

讽刺的是,大多数时候你只需要一个数字:给定容器宽度,这段文本会有多少像素高?这个问题根本不需要触碰 DOM。Pretext 的存在就是为了证明这一点。

traditional-approach.js
// 每次迭代都会强制浏览器布局
const heights = items.map((item) => {
  const el = createElement(item.text);
  document.body.appendChild(el);

  // ← 在此处强制布局回流
  const h = el.getBoundingClientRect().height;
  reflow!

  document.body.removeChild(el);
  return h;
});

// 1000 个项目 = 1000 次回流 = ~94ms ≈ 丢失 6 帧
// 工作原理

Pretext.js 工作原理:测量一次,排版无限次。

拖动分隔线对比传统 DOM 方法与 Pretext。一个每次调用都触发回流,Pretext 使用纯算术运算。

with-pretext.js
import { prepare, layout } from '@chenglou/pretext'

// prepare() 只运行一次 — 使用 Canvas
const prepared = prepare(
  'The quick brown fox jumped.',
  '16px Inter'
);

// layout() 是纯算术运算
const { height, lineCount } = layout(
  prepared,
  containerWidth,
  lineHeight
); 无回流

// 1000 个项目 = 0 次回流 = ~0.05ms
// 快 500 倍,零 DOM 访问
traditional-approach.js
// 每次迭代都会强制浏览器布局
const heights = items.map((item) => {
  const el = createElement(item.text);
  document.body.appendChild(el);

  // ← 在此处强制布局回流
  const h = el.getBoundingClientRect().height;
  reflow!

  document.body.removeChild(el);
  return h;
});

// 1000 个项目 = 1000 次回流 = ~94ms
// ≈ 在 60fps 下丢失 6 帧
Before
After
1

分割文本

标准化空白字符,应用 Unicode 换行规则,使用浏览器自身的文本分割功能将字符串分割为可测量的单元。

2

Canvas 测量

将每个片段通过 Canvas measureText() 获取字体引擎的真实字形前进宽度。结果会被缓存。

3

Pretext.js 纯算术计算

给定容器宽度,通过累加片段宽度计算换行点。行数乘以行高即得高度。完全不需要 DOM。

// 核心能力

Pretext.js 核心能力:为性能关键场景而生。

Pretext 专为构建复杂、文本密集型 UI 的开发者设计,传统 DOM 测量已成为瓶颈。这些能力让它可以投入生产。

零 DOM 读取

在 Pretext 中,prepare() 之后每次 layout() 调用都是纯算术运算。不使用 getBoundingClientRect,不读 offsetHeight,不触发强制同步回流。

🔡

真实字体度量

Pretext 使用浏览器自身的 Canvas 字体引擎测量字形宽度,而非启发式方法或查找表。结果与浏览器实际渲染完全匹配。

🌍

原生多语言支持

Pretext 完整支持中日韩、阿拉伯语、希伯来语、泰语、印地语和韩语书写系统,包括正确的 Unicode 分段和双向文本处理。

📘

TypeScript 原生

从零开始用 TypeScript 编写。每个函数、参数和返回值都有精确的类型定义 — 无需 @types 包,无需类型体操。

♻️

可复用的 prepared 句柄

一次 prepare() 调用覆盖所有容器宽度。从同一个句柄出发,三次算术运算即可计算移动端、平板和桌面端的高度。

📦

零运行时依赖

没有外部包,没有 polyfill,bundle 中没有意外。Pretext 完全依赖每个现代环境中都可用的标准浏览器 API。

// 演示

Pretext.js 真实场景。

Pretext 在传统 DOM 测量失效的场景中表现最为出色。这些示例展示 Pretext 如何解决真实的生产挑战 — 从虚拟滚动到多语言聊天界面。

查看全部 →
// 社区展示

基于 Pretext.js 构建。

来自社区的创意演示 — 文字鼓机、流体模拟、协作白板等。看看开发者们正在用 Pretext 构建什么。

所有展示 →
// 快速开始

三分钟安装 Pretext.js。

Pretext 已发布到 npm,附带完整的 TypeScript 声明。用你喜欢的包管理器安装,导入两个函数,就可以开始无需 DOM 的文本测量了。

$npm install @chenglou/pretext
$pnpm add @chenglou/pretext
$bun add @chenglou/pretext
quickstart.ts
import { prepare, layout } from '@chenglou/pretext'

// 步骤 1: 为给定的文本 + 字体对 prepare 一次
const handle = prepare(
  'Hello, Pretext — no reflow needed.',
  '16px "Inter"'
);

// 步骤 2: 以任何宽度即时 layout
const { height, lineCount } = layout(
  handle,
  400,  // 容器宽度 (px)
  24    // 行高 (px)
);

console.log(height);    // → 48
console.log(lineCount); // → 2

// 步骤 3: 复用 handle 用于不同宽度
const narrow = layout(handle, 240, 24); // → 3 行
// 博客

Pretext.js 博客。

深入探讨文本渲染、浏览器性能,以及 Pretext 如何解决开发者每天都会遇到的问题 — 即使他们不知道根本原因。

全部文章 →
// 下一步

立即试用 Pretext.js。

无论你在构建虚拟滚动列表、代码编辑器、AI 聊天界面,还是任何文本高度很重要的应用 — Pretext 都能给你准确的测量结果,而没有性能代价。

Pretext - 无需触碰 DOM