用 JavaScript 搞定高级排版,这个库太强了

用 JavaScript 搞定高级排版,这个库太强了昨天刷到个叫 pretext 的库 玩了一整天 说实话 一开始我是怀疑的 不就是测量文本高度吗 能有多花哨 结果被打脸了 这个库不仅能测量文本 还能做动态排版 瀑布流 甚至 ASCII 艺术生成 最重要的是 完全不用 DOM 测量 零 reflow 性能炸裂 传统做法有多坑 以前要测量文本高度 我们都是这样 const div document

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



昨天刷到个叫 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
  • 图片位置可以动态调整,实时重字
  • 像专业杂志排版一样,文字流畅绕过图片

传统瀑布流的痛点

要实现瀑布流虚拟化,必须知道每个卡片的高度。传统做法:

  1. 渲染 DOM
  2. 测量 offsetHeight
  3. 用测高度来计算位置
  4. 卡顿

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)

性能提升

指标 传统做法 pretext 提升 首屏渲染 1200ms 80ms 15倍 滚动帧率 30fps 60fps 2倍 内存占用 高 低 70% - Reflow 数百次 0次 -

痛点

一段文字里有不同样式的片段:粗体、@提及、链接、标签。

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

对比

方法 1000次测高 内存 reflow DOM 测量 800ms 高 1000次 pretext 150ms 低 0次

实际场景

虚拟列表里,用户滚动一屏(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 做个虚拟聊天列表,感兴趣可以关注一下。

小讯
上一篇 2026-04-17 09:09
下一篇 2026-04-17 09:07

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/266714.html