題幹與適用場景
線上文件閱讀器要顯示搜尋結果、批註與目前命中項。不能為每個命中插入 span,以免破壞選取、編輯和版面。請設計 CSS Custom Highlight API,說明 Range 管理、註冊表更新、層疊、回退與效能驗收。核心考察文字渲染與漸進增強,歸為 frontend。
面試官考察點
- 區分 Range、Highlight 與
::highlight()。 - 處理重渲染後 Range 失效和增量更新。
- 理解高亮不改 DOM 及與
::selection的層疊。 - 設計顏色、目前命中和鍵盤導覽的無障礙方案。
- 提供不支援瀏覽器回退並量化效能。
回答前需要釐清的問題
- 目標瀏覽器和 WebView 是否支援 API?
- 文件是否會編輯、虛擬化或頻繁替換節點?
- 同時最多多少命中,是否跨文字節點?
- 是否需要暗色、強對比度與讀屏支援?
- 不支援時可否接受 DOM 標記或只顯示結果列表?
30 秒回答框架
「我把每個命中建成 Range,放入 Highlight,再以 CSS.highlights 註冊名稱,由 ::highlight(name) 統一繪製。文字更新後刪除舊 Range 並按文字版本重建,避免保留脫離 DOM 的節點。顏色遵循對比度,當前命中另設樣式和鍵盤導覽。不支援時回退到安全標記節點,並量測命中數、更新耗時、FPS 和記憶體。」
分步驟深入解答
第一步:建立 Range 與 Highlight
API 讓 JavaScript 建立文字 Range,再把多個 Range 組成 Highlight 並註冊到 HighlightRegistry。CSS 用 ::highlight(name) 設定樣式,不必改動原文 DOM。
第二步:處理跨節點與更新
結果保存文件版本、節點路徑和偏移。重渲染或編輯後舊 Range 可能失效;先移除舊 Highlight,再依新文字索引重建,不能只保存節點引用。
第三步:管理多層級
搜尋、批註、目前命中和拼字提示使用不同名稱,並確認與 ::selection 等 UA 高亮的層疊。不要用一個物件覆蓋所有顏色。
第四步:設計無障礙
顏色不是唯一資訊。提供目前命中計數、可聚焦導覽、焦點環與強對比度替代。高亮不一定被讀屏朗讀,結果列表要同步提供語義。
第五步:控制效能
對輸入 debounce、批次更新註冊表、限制可見窗口,量測掃描時間、更新 p95、FPS 和記憶體。避免每個按鍵逐一 set。
第六步:漸進增強回退
能力檢測後選擇 Custom Highlight、DOM 包裹或純文字結果列表。DOM 回退要轉義文字、避免重複巢狀,服務端先輸出可讀原文。
第七步:測試與驗收
覆蓋跨節點、重渲染、編輯、暗色、強對比度、複製、虛擬列表、RTL 和不支援瀏覽器,比較結果、鍵盤順序、首屏時間、FPS、p95 與記憶體。
高品質示範回答
「我會用 Range 表示命中、Highlight 聚合,再透過 CSS.highlights 和 ::highlight() 繪製,保持原文 DOM 不變。結果保存文字版本與偏移,重渲染後刪除舊註冊並重建。搜尋、批註、目前命中分層,當前命中提供計數、焦點和語義狀態。先做能力檢測,不支援時回退安全 DOM 標記或結果列表;用 debounce、批次更新與可見窗口控制成本。」
常見錯誤
- 每個命中插入 span → 破壞選取與結構 → 優先 Range/Highlight。
- 保存舊 Range → 重渲染後失效 → 按文字版本重建。
- 只用顏色 → 強對比度使用者難辨識 → 提供狀態與焦點。
- 逐鍵更新 → 造成抖動 → debounce 和批次更新。
- 回退拼接 HTML → 有注入和巢狀風險 → 轉義並限制範圍。
追問及應對
追問一:為何不用 span?
Custom Highlight 不改 DOM,較少干擾選取、編輯與版面;span 可作回退但要轉義。
追問二:編輯後 Range 怎麼辦?
依新文字版本和偏移重建,不能繼續使用舊節點引用。
追問三:讀屏會朗讀高亮嗎?
不能假設,需以語義文字、計數和鍵盤導覽表達狀態。
追問四:如何證明沒有效能回退?
固定文件和命中數,比較更新 p95、FPS、記憶體、複製和虛擬化場景。