PretextText MeasurementVirtualization效能優化前端教學

Pretext 教學:前端 multiline text measurement 與 layout 實戰

認識剛發布的 Pretext(@chenglou/pretext),掌握 prepare、layout、layoutWithLines、walkLineRanges 的正確用法,評估它和 DOM measurement 在 virtualization 與 shrink-wrap layout 的適用邊界。

· 4 分鐘閱讀

這篇文章會帶你用正確 API 理解 Pretext,並判斷哪些情境值得導入、哪些情境不需要。


前言

前端效能優化常把注意力放在「少 render 一次」或「少做一次 network request」,但在動態文字很多的介面,瓶頸常是更底層的 text measurement。只要你在執行流程中反覆讀 offsetHeightgetBoundingClientRect(),就可能把 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 把流程拆成兩段:

  1. prepare() / prepareWithSegments():一次性的文字分析與測量準備。
  2. 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)做完整驗證。