项目详情

Pretext

不碰 DOM 就知道这段文字有多高——文本测量的 20 年难题,被一个纯 JS 库解干净了

GitHub 地址 在线 Demo MIT 50.2k Stars / 半年 TypeScript · 零 DOM 依赖

更新于 2026-09-04

← 返回汇总
项目速览

一句话定位

文字有多高,算出来不问浏览器

纯 JS/TS 的多行文本测量与排版库:绕开 getBoundingClientRect 这类触发浏览器 reflow 的贵操作,自己实现断行逻辑,用浏览器 canvas 字体引擎做测量基准。一次 prepare 缓存好每个片段宽度,之后 layout 就是纯算术。支持 CJK、阿拉伯语 bidi、emoji、软连字符等「你都不知道存在」的语言特性。适合虚拟化列表、masonry、Canvas 排版、以及 AI 时代不开浏览器就验证文本 UI 的场景。作者 Cheng Lou(react-motion),架构源自 Sebastian Markbåge 的种子设计。

项目速览

核心数据

50.2k
Stars(半年)
2,732
Forks
152
Watchers
91
Open Issues
2026-09-03
最近提交(昨天)
5
昨日 commits
2026-03-07
创建日期
1.6MB
仓库体积

数据来源:GitHub API,采集于 2026-09-04。半年 50k stars 是现象级速度;MIT 协议;npm 包 @chenglou/pretext;尚无正式 release tag(活跃开发期)。

为什么存在

DOM 文本测量是前端 20 年的暗坑

reflow 是最贵的操作

想知道一段文字在容器里折成几行、多高?唯一官方途径是把文字塞进 DOM 再量——触发 layout reflow,列表一长就是性能悬崖。

虚拟化被「猜」绑架

虚拟列表/masonry 需要提前知道每项高度。测不准就只能估高度 + 缓存 + 滚动跳位补偿——各种框架的滚动抖动问题根源都在这。

Canvas 排版没有现成方案

想在 canvas/WebGL 里排多行文字?浏览器只给你 measureText(单行宽度),断行、CJK、bidi 全要自己写——每个团队抄一份烂版本。


Pretext 的答案:断行逻辑自己实现(可控、可缓存),字宽测量借浏览器 canvas 字体引擎(不重造光栅化,天然和 CSS 渲染一致)。 measuring 变成纯算术——这就是它的全部秘密。

核心机制

一次贵的 prepare,终身便宜的 layout

prepare(text, font) —— 贵,只跑一次 规范化空白 · Intl.Segmenter 分段(CJK 逐字 / 拉丁成词) 胶水规则 · canvas measureText 逐段量宽(浏览器字体引擎做基准) 宽度缓存进 opaque 句柄 PreparedText 文本/字体不变就不用重跑;换行宽、行高都不影响它 传句柄 layout(prepared, width, lineHeight) —— 纯算术 贪心断行:查缓存宽度累加,超宽即换行 零 DOM 触碰 · 零 reflow · 微秒级热路径 resize 只需重跑 layout(窗口变化高频安全) 返回 height / lineCount / 每行明细——随你怎么折腾
预处理层(分段 + 测量 + 缓存) 热路径层(纯算术断行)
API 两层

量高度,或者亲手排每一行

用例一:只想要高度(虚拟化/masonry)
import { prepare, layout } from
  '@chenglou/pretext'

const p = prepare(
  'AGI 春天到了. بدأت الرحلة 🚀',
  '16px Inter')
const { height, lineCount } =
  layout(p, 320, 20)
// 纯算术,无 DOM 无 reflow

pre-wrap / word-break: keep-all / letter-spacing 均作为 prepare 选项对齐 CSS 行为。

用例二:手动排每一行(Canvas/SVG/WebGL)
const p = prepareWithSegments(text, font)
const { lines } = layoutWithLines(p, 320, 26)
lines.forEach((l, i) =>
  ctx.fillText(l.text, 0, i * 26))

// 变宽排版:文字绕开浮动图片
const w = y < image.bottom
  ? colW - image.width : colW
const r = layoutNextLineRange(p, cur, w)

walkLineRanges / measureLineStats 不分配字符串,专为二分搜索最优容器宽(shrink-wrap)设计——这个能力 web 平台一直没有。

语言能力

「你都不知道存在」的语言都照顾到了

  • Intl.Segmenter 图素分段:CJK 逐字断行、拉丁文按词断行、emoji ZWJ 序列不劈开
  • 阿拉伯语/希伯来 bidi:混排段落级双向文本,弱方向字符在段落分隔符处重置状态
  • 软连字符(SHY):作为可选断点——被选中才显示成行尾 -,没选中保持隐形
  • keep-all:韩文/无空格混排按 CSS 语义整体断行,超长串仍有 break-word 兜底
  • 富文本内联流:rich-inline 辅助器处理 chips/mentions 原子断行(break: never + extraWidth 给胶囊装饰留宽)
README 开场即秀肌肉
prepare('AGI 春天到了. بدأت الرحلة 🚀',
        '16px Inter')

中文 + 英文 + 阿拉伯语(RTL)+ emoji 混排一句话,这是文本测量库最难啃的凑齐款。

诚实的边界(Caveats)
  • 不做完整字体引擎:optical sizing、font-feature-settings 不建模
  • system-ui / -apple-system 在 macOS 上测不准(浏览器 bug),须用具名字体
  • 依赖 Intl.Segmenter——没有它的运行时直接不支持
  • PLATFORM_BUGS.md:给 Chrome/Firefox/Safari 的文本 bug 记台账
时代红利

为什么半年 50k stars

  • AI 编码的完美搭档:README 原话「very AI-friendly iteration method」——Agent 写 UI 时不开浏览器就能验证「按钮文字会不会溢出换行」,反馈环从秒级降到毫秒级
  • 虚拟化的圣杯拼图: TanStack Virtual 等库喊了多少年「精确动态高度」,pretext 把最后一块测量成本打下来了
  • 作者血统:Cheng Lou(react-motion/react-native-animated)+ Sebastian Markbåge(React core,十年前的 text-layout 种子项目)——前端圈子为信任买单
  • API 品味:prepare/layout 两阶段拆分、opaque 句柄、不分配字符串的 walker——典型的「把贵和便宜切开」的 React 系设计美学
适用清单(README 原文归纳)
  • 虚拟化 / occlusion:不要猜高度不要缓存失效
  • masonry、JS 版 flex、微调布局不写 CSS hack
  • 新文字加载时锚定滚动位置防跳动
  • 开发期(尤其 AI 生成 UI)验证标签不溢出
  • Canvas / SVG / WebGL 排版、服务端排版(路线图)
风险与局限

冷静看

潜力评估

打分

维度评分依据
问题价值
reflow 是前端最贵操作,文本测量是虚拟化圣杯的最后一块
方案优雅度
自实现断行 + 借浏览器字体引擎做基准,贵/便宜分离干净
工程诚实度
Caveats 清单 + PLATFORM_BUGS 台账,边界写得清清楚楚
成熟度
半年新项目,无 release,API 仍在演进
时代势能
AI 生成 UI 的验证缺口正好被它补上,势能在自己脚下

综合 9/10。可能成为下一个「所有 UI 库默默依赖」的地基层——react-motion 作者的第二个 legend 作品正在发生。

最终裁决

适合谁,不适合谁

用
  • 做虚拟列表 / masonry,受够了高度猜测与滚动抖动
  • Canvas/WebGL 应用要排多行文字
  • AI 编码工作流:想不开浏览器就验证文本布局
  • 聊天流/文档编辑器:pre-wrap、软连字符、富文本内联流都是现成的
别用
  • 需要 font-feature-settings / 复杂变体字体排版的印刷级场景
  • 运行时没有 Intl.Segmenter(老浏览器/受限 SSR)
  • 追求 API 永久稳定的生产系统(无 release,还在变)

上一个
Awesome Agent Harness
下一个
TimesFM
在线体验

迷你断行测量台

亲手体验「纯算术测文字」:输入中英阿混合文本,拖宽度滑块,实时看到贪心断行的每一行、行数、最宽行与总高度——旁边同步渲染同款 DOM,肉眼对照精度。这正是 prepare→layout 两阶段在做的事。

进入 Playground →
1 / 12