
Pretext 是一个纯 JavaScript 文本测量库,通过 Canvas API 缓存字符宽度,支持在不改动 DOM 的情况下快速计算文本高度和行数。适合虚拟列表、动态排版等性能敏感场景。
为什么需要 Pretext?
前端开发中,文本测量是虚拟列表、自适应布局等功能的基石。传统方案需要:
- 创建隐藏的 DOM 元素
- 插入文本
- 读取
offsetHeight/getBoundingClientRect() - 触发浏览器布局计算(Layout)
这种方式在大数据量或频繁更新时性能堪忧。
Pretext 的解决方案: 用 Canvas API 一次性测量所有字符宽度,后续计算纯算术完成,不触发布局回流。

核心 API
1. prepare + layout --- 快速测量
最基础的用法,适合只需要总高度和行数的场景。
typescript
import { prepare, layout } from '@chenglou/pretext'
const text = 'AGI 春天到了. Howe est? 🚀'
const font = '16px Inter'
const lineHeight = 24
// 一次性分析文本,返回不透明句柄
const prepared = prepare(text, font)
// 纯算术计算,不触发布局
const result = layout(prepared, 300, lineHeight)
console.log(result.height) // 总高度
console.log(result.lineCount) // 总行数
typescript
// 输出示例
// { height: 48, lineCount: 2 }
使用场景: 虚拟列表的 item 高度计算、聊天气泡的自适应高度。
2. prepareWithSegments + layoutWithLines --- 获取行详情
需要知道每行具体内容的场景。
typescript
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext'
const text = 'Hello World! This is Pretext.'
const prepared = prepareWithSegments(text, '16px Inter')
// 返回每行的详细信息
const { height, lineCount, lines } = layoutWithLines(prepared, 200, 24)
lines.forEach((line, i) => {
console.log(`Line ${i + 1}: "${line.text}" (${line.width}px)`)
})
typescript
// 输出示例
// Line 1: "Hello World!" (81px)
// Line 2: "This is" (42px)
// Line 3: "Pretext." (60px)
使用场景: 文本编辑器行号显示、代码高亮的行对齐。
3. walkLineRanges --- 回调遍历
需要逐行处理,每行触发一次回调。
typescript
import { prepareWithSegments, walkLineRanges } from '@chenglou/pretext'
const prepared = prepareWithSegments(text, '16px Inter')
// 遍历每一行,执行自定义逻辑
const lineWidths: number[] = []
walkLineRanges(prepared, 300, 24, (line) => {
lineWidths.push(line.width)
console.log(`"${line.text}" starts at ${line.start}, ends at ${line.end}`)
})
console.log('All widths:', lineWidths)
使用场景: 查找最长行、收集行统计信息。
4. layoutNextLine --- 迭代器模式
逐行获取,可从任意位置开始,适合流式布局。
typescript
import { prepareWithSegments, layoutNextLine } from '@chenglou/pretext'
const prepared = prepareWithSegments(text, '16px Inter')
let cursor = { paragraph: 0, secondLine: 0 }
while (true) {
const line = layoutNextLine(prepared, cursor, 300)
if (line === null) break
console.log(`"${line.text}" (${line.width}px)`)
cursor = line.end // 关键:使用上一行的结束位置继续
}
使用场景: 流式文本渲染、增量加载文本。
5. whiteSpace: 'pre-wrap' 选项
保留换行和缩进。
typescript
const codeText = `function hello() {
console.log('Hello')
}`
const prepared = prepare(codeText, '14px "Fira Code"', { whiteSpace: 'pre-wrap' })
const { height, lineCount } = layout(prepared, 300, 20)
Vue 3 集成示例
typescript
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { prepare, layout, prepareWithSegments, layoutWithLines } from '@chenglou/pretext'
const content = ref('')
const containerWidth = ref(400)
const lineHeight = 24
const font = '16px Inter'
// 文本内容变化时重新计算
function calculateHeight() {
const prepared = prepare(content.value, font)
return layout(prepared, containerWidth.value, lineHeight)
}
// 获取行详情
function getLines() {
const prepared = prepareWithSegments(content.value, font)
return layoutWithLines(prepared, containerWidth.value, lineHeight)
}
const height = ref(0)
const lineCount = ref(0)
const lines = ref([])
function update() {
const result = calculateHeight()
height.value = result.height
lineCount.value = result.lineCount
lines.value = getLines().lines
}
onMounted(update)
</script>
<template>
<div>
<textarea v-model="content" @input="update" />
<p>高度: {{ height }}px, 行数: {{ lineCount }}</p>
<div v-for="(line, i) in lines" :key="i">
{{ i + 1 }}: {{ line.text }} ({{ line.width }}px)
</div>
</div>
</template>
demo预览
somnai-dreams.github.io/pretext-dem...
性能对比
| 方案 | 1000 次测量耗时 | 是否触发布局 |
|---|---|---|
原生 DOM (offsetHeight) |
~800ms | 是 |
| Pretext (首次 prepare) | ~50ms | 一次性 |
| Pretext (后续 layout) | ~1ms | 否 |
Pretext 的首次 prepare 稍慢(需测量字符),但后续 layout 调用极快(纯算术)。
注意事项
-
font 字符串必须匹配 :确保
prepare()的 font 参数与实际 CSS 渲染完全一致,包括字号、字重、字体族。 -
lineHeight 必须一致 :
layout()的 lineHeight 参数需与 CSSline-height声明值相同。 -
不支持的 CSS 特性 :不支持
letter-spacing、word-spacing扩展、部分 Unicode 字符可能测量不准。
适用场景
- 虚拟列表/虚拟滚动
- 聊天应用的消息气泡
- 动态排版系统
- 任何需要提前知道文本尺寸的场景
不适用场景
- 包含
letter-spacing/word-spacing的文本 - 复杂的富文本(图片、链接混排)
- 需要像素级精确的场景(建议实测验证)
安装
bash
npm install @chenglou/pretext
总结
Pretext 通过将文本测量从「运行时查询 DOM」转变为「一次性测量 + 缓存算术」,为性能敏感的文本布局场景提供了可行方案。API 设计简洁,分层清晰,从基础的高度查询到细粒度的行迭代都有覆盖。