昨天刷到个叫 pretext 的库,玩了一整天。
说实话,一开始我是怀疑的:“不就是测量文本高度吗,能有多花哨?”
结果被打脸了。这个库不仅能测量文本,还能做动态排版、瀑布流、甚至 ASCII 艺术生成。
最重要的是:完全不用 DOM 测量,零 reflow,性能炸裂。
传统做法有多坑
以前要测量文本高度,我们都是这样:
const div = document.createElement(‘div’) div.textContent = text div.style.font = ‘16px Inter’ div.style.width = ‘320px’ document.body.appendChild(div) const height = div.offsetHeight // 💥 触发 reflow! document.body.removeChild(div)
每次测一下就要触发一次 layout reflow,一个虚拟列表要测几百个,浏览器就被卡死了。
pretext 的黑科技
pretext 用浏览器的 font engine 作为”基准”,实现了自己的文本测量逻辑,完全不需要 DOM 操作:
import { prepare, layout } from ’@chenglou/pretext’
// 一次性准备(只测一次) const prepared = prepare(‘AGI 春天到了. بدأت الرحلة 🚀’, ‘16px Inter’)
// 布局(纯数学计算,超快!) const { height, lineCount } = layout(prepared, 320, 20) // height: 60, lineCount: 3
prepare() 做了一次性工作:归一化空格、分词、测量段宽,返回一个 opaque handle。
layout() 是热点路径:纯算术运算,复用缓存的宽度,不测 DOM。
痛点
聊天应用里,消息气泡总是要么太宽(浪费空间),要么换行太多(难看)。
效果对比:
传统 CSS 方式:
- 短消息:气泡太宽,左边留大块空白
- 长消息:换行太多,看着累赘
pretext 方式:
- 短消息:气泡紧凑,刚好容纳文本
- 长消息:自动换行,每行充分利用空间
pretext 的方案
用 walkLineRanges() 找到每一行的实际宽度,然后取最大值,就是最紧凑的容器宽度:
import { prepareWithSegments, walkLineRanges } from ’@chenglou/pretext’
const prepared = prepareWithSegments(message, ‘16px Inter’) let maxWidth = 0
walkLineRanges(prepared, 400, line => )
// maxWidth 就是刚好能装下所有文本的最小宽度 // 消息气泡宽度 = Math.min(maxWidth, 屏幕宽度的70%)
效算
- 长消息:“这个 pretext 库太强了” → 气泡宽度 240px(刚好装下)
- 短消息:“你好!” → 气泡宽度 60px(不浪费空间)
- 换行控制:lineCount 和 maxWidth 都可控
这叫 multiline shrink-wrap,Web 原生没有这个能力,pretext 补上了。
效果
像杂志排版那样,文字绕着图片流动,图片在左边时右边文字窄一些,图片结束后恢复正常宽度。
代码
import { prepareWithSegments, layoutNextLineRange, materializeLineRange } from ’@chenglou/pretext’
const prepared = prepareWithSegments(article, ‘17px Inter’) let cursor = { segmentIndex: 0, graphemeIndex: 0 } let y = 0
const imageHeight = 140 const imageWidth = 120 const columnWidth = 320
while (true)
炫酷在哪里
- 完全在 JavaScript 层计算,不用 CSS float hack
- 可以渲染到 Canvas、SVG,甚至 WebGL
- 图片位置可以动态调整,实时重字
- 像专业杂志排版一样,文字流畅绕过图片
传统瀑布流的痛点
要实现瀑布流虚拟化,必须知道每个卡片的高度。传统做法:
- 渲染 DOM
- 测量
offsetHeight - 用测高度来计算位置
- 卡顿
pretext 的方案
卡片高度预先算好,虚拟化时直接用:
import { prepare, layout } from ’@chenglou/pretext’
// 批量计算所有卡片的高度 const cardHeights = cards.map(card => { const prepared = prepare(card.content, ‘16px Inter’) const { height } = layout(prepared, 200, 24) return height + 100 // 文字高度 + 图片高度 + padding })
// 虚拟化滚动时,只用查表 const visibleCards = getVisibleCards(scrollTop, cardHeights)
性能提升
痛点
一段文字里有不同样式的片段:粗体、@提及、链接、标签。
CSS 能做,但想控制每个 fragment 的精确位置和宽度,就得靠 JavaScript。
pretext 的 rich-inline 模块
import { prepareRichInline, walkRichInlineLineRanges, materializeRichInlineLineRange } from ’@chenglou/pretext/rich-inline’
const prepared = prepareRichInline([ { text: ‘Ship ‘, font: ‘500 17px Inter’ }, { text: ’@maya’, font: ‘700 12px Inter’, break: ‘never’, extraWidth: 22 }, // 提及,不换行 { text: ”’s rich-note”, font: ‘500 17px Inter’ }, ])
walkRichInlineLineRanges(prepared, 320, range => { const line = materializeRichInlineLineRange(prepared, range) // line.fragments 里每个 fragment 都有: // - text: 文本内容 // - font: 字体 // - gapBefore: 前置空格宽度 // - cursors: 起止位置 })
效果
@maya标签整体不换行(break: ‘never’)- 标签宽度包含 pill 装饰(
extraWidth: 22) - 换行时边界空格正确处理(不像 CSS 那样容易出错)
- 多种字体混合,精确测量
效果
用等宽字体和比例字体分别渲染 ASCII 艺术,对比效果。
为什么炫酷
pretext 能处理:
- Grapheme 级别:不是 char,是真正的”字”(emoji 算一个)
- 所有语言:中文、阿拉伯语、希伯来语,统统支持
- 组合字符:emoji 修饰符、变体选择器
这意味着你可以用 JavaScript 做任何 Unicode 文本的精确排版。
官方数据
- prepare(): 测 1000 个 100 字的字符串,耗时 ~150ms
- layout(): 同样数据,纯计算,耗时 <1ms
对比
实际场景
虚拟列表里,用户滚动一屏(20个 item):
- DOM 测量:触发 20 次 reflow,200ms
- pretext:全用缓存,<1ms
不只是 DOM
pretext 计算完布局后,可以渲染到:
- Canvas: 适合游戏、数据可视化
- SVG: 适合高质量输出、打印
- WebGL: 适合 3D 混合场景
- Server-side: 未来支持,可以在 Node.js 里预计算
Canvas 渲染示例
import { prepareWithSegments, layoutWithLines } from ’@chenglou/pretext’
const prepared = prepareWithSegments(text, ‘18px “Helvetica Neue”’) const { lines } = layoutWithLines(prepared, 320, 26)
const ctx = canvas.getContext(‘2d’) ctx.font = ‘18px “Helvetica Neue”’
for (let i = 0; i < lines.length; i++) { ctx.fillText(lines[i].text, 0, i * 26) }
适合用 pretext
- 虚拟列表/滚动: 需要提前算高度
- 自定义布局: masonry、瀑布流、异形容器
- Canvas/SVG 渲染: 不能用 DOM
- 复杂富文本: 多字体、多样式混合
- 多语言支持: 阿拉伯语、中文等混合
- AI 辅助开发: 需要浏览器无关的文本测量
不适合
- 简单的单行文本测量(CSS line-height 够用)
- 静态页面,不需要动态计算
- 只需要浏览器原生能力覆盖的场景
说实话,pretext 最大的价值不是”能做多少事情”,而是“不做 DOM 测量”。
浏览器的 layout reflow 是最贵的操作之一,能避开就避开。
现在有了 pretext,我们在 JavaScript 层就能搞定大部分文本布局问题,而且还能渲染到 Canvas、SVG,甚至未来能在服务器端预计算。
这对于:
- 想做高级 UI 的前端
- 需要 Canvas 渲染的游戏开发者
- 做 AI 辅助开发的工具作者
都是福音。
npm 地址:@chenglou/pretext
下一步打算用 pretext 做个虚拟聊天列表,感兴趣可以关注一下。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/266714.html