醒目顯示文字範圍

網頁功能: 自訂重點

CSS Custom Highlight API 可讓您設定網頁上任意文字範圍的樣式,而不必修改 DOM 結構。這樣一來,您就能醒目顯示搜尋結果、為語法上色、顯示協作編輯游標,或標示拼字和文法錯誤,不必將文字包裝在額外元素中,或依賴 innerHTML 操控。

核心實作

如要醒目顯示文字範圍,您必須收集目標文字節點、建立 Range 和 Highlight 物件、在 HighlightRegistry 中註冊這些物件,然後使用 ::highlight() 虛擬元素設定樣式。

1. 收集文字節點並建立範圍

使用 TreeWalker 收集目標元素中的所有文字節點,然後建立 Range 物件,指向要醒目顯示的字元偏移。

const article = document.querySelector("article");

// MANDATORY: Use TreeWalker to collect text nodes — do not manipulate innerHTML.
const treeWalker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT);
const allTextNodes = [];
let currentNode = treeWalker.nextNode();
while (currentNode) {
  allTextNodes.push(currentNode);
  currentNode = treeWalker.nextNode();
}

// MANDATORY: Set range start/end on text nodes, not element nodes.
const range = new Range();
range.setStart(textNode, matchStartIndex);
range.setEnd(textNode, matchEndIndex);

快取文字節點清單,並僅在 DOM 內容實際變更時重建,因為遍歷樹狀結構的成本很高。

2. 根據範圍建立精華片段

將一或多個 Range 物件分組為 Highlight。樣式相同的多個範圍屬於同一個醒目顯示。

const searchHighlight = new Highlight(...matchingRanges);

3. 在登錄檔中註冊醒目顯示

使用 CSS.highlights (類似 Map 的 HighlightRegistry),以自訂名稱註冊每個 Highlight。

// MANDATORY: Clear previous highlights before registering new ones
// to avoid stale ranges persisting on the page.
CSS.highlights.clear();

CSS.highlights.set("search-results", searchHighlight);

如果有多個醒目顯示重疊,請使用 priority 屬性控制堆疊順序。優先順序較高的醒目顯示會疊在上方。

const primary = new Highlight(...primaryRanges);
primary.priority = 1;

const secondary = new Highlight(...secondaryRanges);
secondary.priority = 0; // painted first (behind primary)

CSS.highlights.set("primary", primary);
CSS.highlights.set("secondary", secondary);

4. 使用 ::highlight() 設定樣式

在 CSS 中使用 ::highlight() 虛擬元素,依名稱設定每個註冊醒目顯示的樣式。

::highlight(search-results) {
  background-color: #ffdd00;
  color: black;
}

::highlight() 內僅支援一組有限的 CSS 屬性:color、background-color、text-decoration 及其長寫、text-shadow、-webkit-text-stroke-color、-webkit-text-fill-color 和 -webkit-text-stroke-width。系統會忽略 background-image、font-size 或 padding 等屬性。

無障礙設定

AVOID:使用自訂重點取代語意式 HTML。

自訂重點僅用於呈現,不會向無障礙樹狀結構公開。如果醒目顯示的文字與文件語意相關 (例如使用者選取的段落),請改用 <mark>。請將自訂重點保留給暫時性、僅限視覺的效果,例如搜尋結果或語法著色。

醒目顯示不應只依賴顏色來傳達意義。如果醒目顯示代表錯誤,請搭配其他視覺指標,例如 text-decoration: wavy underline 或相鄰的文字標籤。醒目顯示背景和文字顏色之間必須有足夠的對比度,符合 WCAG 2.1 規定 (一般文字至少 4.5:1)。

備用策略

如果瀏覽器不支援 CSS Custom Highlight API,請提供功能性基本體驗,確保文字即使沒有視覺醒目顯示,仍可清楚辨識。

您可以在使用 API 前偵測是否支援:

if (CSS.highlights) {
  // CSS Custom Highlight API is supported.
} else {
  // Fallback: wrap matches in <mark> elements.
}

如果醒目顯示對使用者體驗至關重要,請改為將相符文字包裝在 <mark> 元素中。這會修改 DOM,因此請務必保留事件監聽器,並避免破壞文件結構。

if (!CSS.highlights) {
  // Walk text nodes and wrap matches in <mark>, preserving structure.
  const walker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT);
  const nodes = [];
  for (let n = walker.nextNode(); n; n = walker.nextNode()) nodes.push(n);

  const term = searchTerm.toLowerCase();
  for (const textNode of nodes) {
    const text = textNode.textContent;
    let pos = text.toLowerCase().indexOf(term);
    if (pos === -1) continue;

    const frag = document.createDocumentFragment();
    let last = 0;
    while (pos !== -1) {
      frag.append(text.slice(last, pos));
      const mark = document.createElement("mark");
      // textContent assignment avoids HTML injection.
      mark.textContent = text.slice(pos, pos + term.length);
      frag.append(mark);
      last = pos + term.length;
      pos = text.toLowerCase().indexOf(term, last);
    }
    frag.append(text.slice(last));
    textNode.replaceWith(frag);
  }
}