Evidenzia intervalli di testo

Funzionalità web: Evidenziazioni personalizzate

L'API CSS Custom Highlight consente di applicare stili a intervalli di testo arbitrari in una pagina senza modificare la struttura del DOM. In questo modo è possibile evidenziare i risultati di ricerca, colorare la sintassi, utilizzare i cursori di modifica collaborativa o i marcatori di errori ortografici e grammaticali senza racchiudere il testo in elementi aggiuntivi o fare affidamento sulla manipolazione di innerHTML.

Implementazione di base

Per evidenziare gli intervalli di testo, devi raccogliere i nodi di testo di destinazione, creare oggetti Range e Highlight, registrarli in HighlightRegistry e poi applicare lo stile con lo pseudo-elemento ::highlight().

1. Raccogli i nodi di testo e crea gli intervalli

Utilizza un TreeWalker per raccogliere tutti i nodi di testo nell'elemento di destinazione, quindi crea oggetti Range che puntano agli offset dei caratteri che vuoi evidenziare.

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);

Memorizza nella cache l'elenco dei nodi di testo e ricrealo solo quando i contenuti del DOM cambiano effettivamente, poiché l'esplorazione dell'albero è un'operazione dispendiosa.

2. Crea un momento saliente dagli intervalli

Raggruppa uno o più oggetti Range in un Highlight. Più intervalli che condividono lo stesso stile appartengono a un'unica evidenziazione.

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

3. Registra l'evidenziazione nel registro

Registra ogni Highlight con un nome personalizzato utilizzando CSS.highlights, che è un HighlightRegistry simile a Map.

// 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);

Quando più evidenziazioni si sovrappongono, utilizza la proprietà priority per controllare l'ordine di sovrapposizione. Le evidenziazioni con priorità più alta vengono visualizzate sopra.

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. Stile con ::highlight()

Utilizza lo pseudo-elemento ::highlight() in CSS per applicare uno stile a ogni evidenziazione registrata in base al nome.

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

Solo un insieme limitato di proprietà CSS funziona all'interno di ::highlight(): color, background-color, text-decoration e le relative proprietà estese, text-shadow, -webkit-text-stroke-color, -webkit-text-fill-color e -webkit-text-stroke-width. Le proprietà come background-image, font-size o padding vengono ignorate.

Accessibilità

AVOID: l'utilizzo di evidenziazioni personalizzate in sostituzione dell'HTML semantico.

Le evidenziazioni personalizzate sono puramente di presentazione e non vengono esposte all'albero di accessibilità. Se il testo evidenziato è semanticamente pertinente al documento (ad esempio, un passaggio selezionato dall'utente), utilizza invece <mark>. Riserva le evidenziazioni personalizzate per effetti temporanei e solo visivi, come i risultati di ricerca o la colorazione della sintassi.

Le evidenziazioni non devono basarsi esclusivamente sul colore per trasmettere il significato. Se un'evidenziazione indica un errore, abbinala a un altro indicatore visivo, ad esempio text-decoration: wavy underline o un'etichetta di testo adiacente. Assicurati che il contrasto tra lo sfondo dell'evidenziazione e il colore del testo sia sufficiente per soddisfare i requisiti WCAG 2.1 (almeno 4,5:1 per il testo normale).

Strategie di riserva

Per i browser che non supportano l'API CSS Custom Highlight, devi fornire un'esperienza di base funzionale in cui il testo sia comunque leggibile, anche senza l'evidenziazione visiva.

Puoi rilevare il supporto prima di utilizzare l'API:

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

Se l'evidenziazione è fondamentale per l'esperienza utente, utilizza il wrapping del testo corrispondente negli elementi <mark>. In questo modo, il DOM viene modificato, quindi assicurati di preservare i listener di eventi ed evitare di interrompere la struttura del documento.

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);
  }
}