纯 JS · 零 DOM 读取 · TypeScript 优先

Pretext.js

无需触碰 DOM

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

14k+
Pretext.js 的 GitHub stars
0
Pretext.js layout() 中的 DOM 读取
~2ms
Pretext.js 排版 1,000 个文本块
12+
Pretext.js 支持的书写系统
// 问题所在

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

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

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

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

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.js。一个每次调用都触发回流,Pretext.js 使用纯算术运算。

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.js 专为构建复杂、文本密集型 UI 的开发者设计,传统 DOM 测量已成为瓶颈。这些能力让它可以投入生产。

零 DOM 读取

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

🔡

真实字体度量

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

🌍

原生多语言支持

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

📘

TypeScript 原生

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

♻️

可复用的 prepared 句柄

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

📦

零运行时依赖

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

// 示例

Pretext.js 真实场景。

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

查看全部 →
// 快速开始

三分钟安装 Pretext.js。

pretext.js 已发布到 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.js — 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.js 如何解决开发者每天都会遇到的问题 — 即使他们不知道根本原因。

全部文章 →
// 下一步

立即试用 Pretext.js。

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

Pretext.js - 无需触碰 DOM