📋 目錄
這篇文章會帶你用正確 API 理解 Pretext,並判斷哪些情境值得導入、哪些情境不需要。
前言
前端效能優化常把注意力放在「少 render 一次」或「少做一次 network request」,但在動態文字很多的介面,瓶頸常是更底層的 text measurement。只要你在執行流程中反覆讀 offsetHeight、getBoundingClientRect(),就可能把 layout reflow 拉進主執行緒,讓滾動和互動明顯卡頓。
Pretext(@chenglou/pretext)是 2026 年 3 月才出現的新 library:npm package 建立於 2026-03-27,最新版本 0.0.3 發布於 2026-03-29。它的定位不是「新標準」,而是值得觀察的早期工具,目標很明確:把「估高度、量行數」從 DOM read 轉成可重複的純計算流程。
另外,Pretext 並不是憑空出現的新點子。官方 Credits 也提到,這套設計延續了 Sebastian Markbage 早期 text-layout 的思路,再往前推進成目前這個可直接給前端專案使用的 API 形態。理解這段脈絡,有助於你判斷它屬於「持續演進中的工程路線」,而不是一個只靠行銷包裝的短期名詞。
如果你正在做訊息串、評論流、可變高度卡片、文字驅動的 virtualization,Pretext 是一個值得實驗的選項。重點是「看懂它能解什麼問題」,而不是把它套到所有文字 UI。
Pretext 是什麼,解決哪些痛點
Pretext 把流程拆成兩段:
prepare()/prepareWithSegments():一次性的文字分析與測量準備。layout()/layoutWithLines()/walkLineRanges():針對不同寬度快速計算高度、行數或行區間。
這種拆法對三種場景特別有價值:
- Virtualization:先算好每筆內容高度,render 時直接查值,避免每列都做 DOM measurement。
- Multiline text measurement:同一段文字要反覆試不同寬度時,不必每次重跑完整流程。
- Shrink-wrap layout:你可以先走 line ranges,找出理想寬度,再決定最終排版。
核心 API 實作範例(正確版)
1. prepare() + layout():先取得高度與行數
import { prepare, layout } from '@chenglou/pretext';
const text = 'AGI 春天到了,前端排版還是要面對真實字體與真實內容。';
// 建議使用明確字型名稱,避免 system-ui 在 macOS 的精度風險
const prepared = prepare(text, '16px "Helvetica Neue"');
// 正確簽名:layout(prepared, maxWidth, lineHeight)
const result = layout(prepared, 320, 24);
console.log(result.height); // 例如 72
console.log(result.lineCount); // 例如 3
2. prepareWithSegments() + layoutWithLines():需要逐行資訊時使用
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext';
const prepared = prepareWithSegments(
'這段文字要展示逐行結果,方便做 Canvas 或自訂標註。',
'16px "Noto Sans TC"'
);
const { height, lineCount, lines } = layoutWithLines(prepared, 280, 24);
console.log(height, lineCount);
for (const line of lines) {
// lines 欄位是 text / width / start / end
console.log(line.text, line.width, line.start, line.end);
}
3. walkLineRanges():先走行區間,再決定寬度策略
import { prepareWithSegments, walkLineRanges } from '@chenglou/pretext';
const prepared = prepareWithSegments(
'Shrink-wrap 版型常需要先知道每行實際寬度,再反推容器寬度。',
'16px "Noto Sans TC"'
);
let widest = 0;
let lineCount = 0;
// 正確簽名:walkLineRanges(preparedWithSegments, maxWidth, onLine)
walkLineRanges(prepared, 320, (line) => {
lineCount += 1;
if (line.width > widest) widest = line.width;
});
console.log({ widest, lineCount });
與傳統 DOM measurement 的差異與邊界
傳統流程通常是 render 一段隱藏節點,再讀尺寸。好處是簡單直覺;代價是當資料量大、測量次數高,主執行緒容易被 layout 反覆打斷。
Pretext 的優勢在於把高頻需求轉成可預期的計算流程,特別適合「同一批資料會被反覆量測」的情境。但它不是萬用解:
- 你的列表高度本來就固定,導入收益可能很低。
- 項目數量很少,直接 DOM measurement 也可能足夠。
- 你仍要確保字型載入時機,否則預估結果會偏差。
另外,README 也明確提醒:若你追求 layout() 精度,在 macOS 上不要用 system-ui,請改用具體字型名稱。
常見問題 / 注意事項
Q1:可不可以把 Pretext 丟進 Web Worker?
可以把準備與計算流程安排在背景時機,但這是架構選擇,不是「官方保證自動幫你做」。你的系統仍要自己處理同步、快取與主執行緒資料交換成本。
Q2:何時要用 prepareWithSegments()?
當你需要 layoutWithLines()、walkLineRanges()、layoutNextLine() 這類手動排版能力時,才需要 richer prepared data。只算高度時用 prepare() 即可。
Q3:white-space: pre-wrap 怎麼處理?
Pretext 提供 options,可在 prepare / prepareWithSegments 傳入 { whiteSpace: 'pre-wrap' },保留空白、tab、換行語意。
互動 Demo
已補上可互動的對比頁,能切換資料量、卡片寬度與測試輪數,直接比較兩條路徑耗時:
總結
Pretext 目前比較像「值得觀察與試點」的新興 text layout library,而不是可以直接取代所有 DOM measurement 的銀彈。它最有價值的地方,在於讓你對文字高度與行分布有更穩定的計算管線,特別是在 virtualization 與 shrink-wrap layout。
下一步建議:先做一個可重現的 Pretext vs DOM measurement demo(例如同一批訊息資料,各自走 layout() 與隱藏節點量測),固定資料量與字型條件,對照捲動流暢度與主執行緒忙碌時間。若你在團隊內要推動導入,這類可重現結果會比口語宣稱更有說服力;也可延伸接到後續 demo 實作題(如 FRO-157)做完整驗證。