Pretext 高效多行文字 Canvas 排版庫優化與防 DOM 重繪技術
想精準計算段落高度、做出更自由的文字排版,卻不想一直碰 getBoundingClientRect?Pretext 提供了一條很聰明的路。
為什麼選擇 Pretext?
在前端處理文字排版時,最麻煩的事情之一就是:你明明只是想知道一段文字會佔多高,卻常常得把它真的丟進 DOM 裡量一次。
像 getBoundingClientRect()、offsetHeight 這類做法雖然直覺,但很容易觸發 layout reflow。當畫面裡有大量動態文字、卡片瀑布流、虛擬列表,或是你想做更進階的自訂排版時,這個成本其實不小。
Pretext 是 Cheng Lou 做的純 JavaScript / TypeScript 文字量測與排版函式庫。它的核心思路很漂亮:不依賴 DOM 量測,而是自己做文字分析與快取,再用瀏覽器的字型引擎作為 ground truth。
它支援多語系、支援 white-space 與 word-break 的常見情境,還可以把排好行的結果拿去渲染到 DOM、Canvas、SVG,甚至未來做 server-side。
實作展示 (Official Demo)
這次我其實有先自己試做一下,但目前還沒做出我自己滿意的成品,所以文章先放官方大神做的 demo,真的非常猛。
下面這段影片是我放進部落格的展示:
這個 demo 對應的是 Somnai Dreams 做的 The Editorial Engine:
如果你想看更多範例,也可以直接去作者提供的展示頁:
如何在你的專案中使用?
1. 安裝套件
你可以直接安裝 npm 套件:
npm install @chenglou/pretext
2. 核心代碼實作
如果你的需求只是:在不碰 DOM 的情況下,預先算出一段文字在某個寬度下會佔幾行、多高,那最基本的用法其實很簡單:
import { prepare, layout } from '@chenglou/pretext'
const prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀', '16px Inter')
const { height, lineCount } = layout(prepared, 320, 20)
console.log(height, lineCount)
prepare() 會先做一次性的預處理,包括文字分段、空白正規化、量測 segment 寬度與快取;layout() 則是後續的快速熱路徑,單純根據寬度與行高做算術計算。
這代表當你的容器寬度改變時,不需要一直重新分析全文字,通常只要重跑 layout() 就好。
進一步玩法:自己掌控每一行
如果你不只想知道高度,而是想要自己控制每一行的內容,例如:
- 讓文字繞圖
- 做 Canvas / SVG 排版
- 自己決定每一行的寬度
- 做 multiline shrink-wrap
那就可以改用 prepareWithSegments() 與 layoutWithLines():
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext'
const prepared = prepareWithSegments(
'AGI 春天到了. بدأت الرحلة 🚀',
'18px "Helvetica Neue"'
)
const { lines } = layoutWithLines(prepared, 320, 26)
for (let i = 0; i < lines.length; i++) {
console.log(lines[i].text, lines[i].width)
}
這種 API 很適合拿去做比較「設計導向」的文字編排,而不是只能接受瀏覽器幫你決定一切。
我覺得 Pretext 厲害的地方
1. 先預處理,之後只做便宜計算
這個設計很適合 responsive UI。文字內容沒變時,你可以把 prepare() 的結果保留起來,視窗縮放只更新 layout()。
2. 對多語系文字更友善
官方範例直接拿中英阿拉伯文和 emoji 混排,這點很有說服力。很多看似簡單的文字排版工具,一碰到多語混排就開始露出破綻。
3. 很適合做 AI 時代的快速 UI 驗證
README 裡有一個觀點我很認同:現在很多 UI 是快速迭代、甚至直接讓 AI 幫你生畫面,這時候如果能在不開瀏覽器、不碰 DOM的前提下預先驗證文字會不會爆行,真的很實用。
4. 不只量高度,還能拿來做自訂排版引擎
像 layoutNextLineRange()、materializeLineRange() 這些 API,其實已經不是單純量測工具而已,幾乎是在提供你一套低階但很實用的文字流排版能力。
幾個值得注意的限制
Pretext 很強,但它也沒有假裝自己是完整的瀏覽器排版引擎。
- 目前主要鎖定
white-space: normal/pre-wrap - 支援
word-break: normal與keep-all - 非常窄的寬度下,仍可能在 grapheme 邊界做 break-word 式換行
- 依賴
Intl.Segmenter與 Canvas 2D text measurement - 在 macOS 上,
system-ui對layout()精準度不安全,官方建議用具名字型
所以它最適合的場景不是「完整取代瀏覽器排版」,而是:你想提前知道排版結果,或者你就是要自己接管文字流布局。
個人心得
我很喜歡 Pretext 這種工具的出發點:不是把整個世界重做一遍,而是專注解掉前端裡一個很痛、但大家常常默默忍受的問題。
這次我原本也想自己做一個更完整、比較有作品感的示範,不過老實說目前還沒有做到我滿意,所以先把官方大神的 demo 放上來。Somnai Dreams 那個 The Editorial Engine 真的把 Pretext 的潛力拉得很高,已經不是「技術展示」而已,而是很接近一種新的文字互動介面。
如果你現在剛好在做以下東西,我會很建議研究一下 Pretext:
- 需要預估文字高度的虛擬列表
- 瀑布流或卡片式排版
- Canvas / SVG 文字排版
- 圖文混排或繞圖排版
- 想避免 layout shift 的內容載入介面
小建議:如果你只是 resize 後重新計算高度,記得不要每次都重跑
prepare(),不然就浪費掉 Pretext 最核心的快取優勢了。
相關連結:
本文展示影片採用 Somnai Dreams 製作的 Pretext 官方社群 demo;文中 API 說明整理自 Pretext README 與公開範例。