La API de CSS Custom Highlight te permite aplicar diseño a rangos de texto arbitrarios en una página sin modificar la estructura del DOM. Esto permite destacar los resultados de la búsqueda, colorear la sintaxis, mostrar los cursores de edición colaborativa o marcar los errores ortográficos y gramaticales sin unir el texto en elementos adicionales ni depender de la manipulación del innerHTML.
Implementación principal
Para destacar rangos de texto, debes recopilar los nodos de texto de destino, crear objetos Range y Highlight, registrarlos en el HighlightRegistry y, luego, aplicarles diseño con el seudoelemento ::highlight().
1. Recopila nodos de texto y crea rangos
Usa un TreeWalker para recopilar todos los nodos de texto en el elemento de destino y, luego, crea objetos Range que apunten a los desplazamientos de caracteres que quieras destacar.
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);
Almacena en caché la lista de nodos de texto y vuelve a compilarla solo cuando el contenido del DOM cambie, ya que recorrer el árbol es costoso.
2. Crea un momento destacado a partir de los rangos
Agrupa uno o más objetos Range en un Highlight. Varios rangos que comparten el mismo estilo pertenecen a un solo elemento destacado.
const searchHighlight = new Highlight(...matchingRanges);
3. Registra el elemento destacado en el registro
Registra cada Highlight con un nombre personalizado usando CSS.highlights, que es un HighlightRegistry similar 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);
Cuando se superponen varios elementos destacados, usa la propiedad priority para controlar el orden de apilamiento. Los elementos destacados de mayor prioridad se pintan en la parte superior.
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. Estilo con ::highlight()
Usa el pseudoelemento ::highlight() en CSS para aplicar diseño a cada elemento destacado registrado por nombre.
::highlight(search-results) {
background-color: #ffdd00;
color: black;
}
Solo un conjunto limitado de propiedades de CSS funciona dentro de ::highlight(): color, background-color, text-decoration y sus formas largas, text-shadow, -webkit-text-stroke-color, -webkit-text-fill-color y -webkit-text-stroke-width. Se ignoran las propiedades como background-image, font-size o padding.
Accesibilidad
AVOID usar aspectos destacados personalizados como reemplazo del HTML semántico.
Los aspectos destacados personalizados son puramente de presentación y no se exponen al árbol de accesibilidad. Si el texto destacado es semánticamente relevante para el documento (p.ej., un pasaje seleccionado por el usuario), usa <mark> en su lugar. Reserva los aspectos destacados personalizados para efectos transitorios y solo visuales, como los resultados de la búsqueda o el color de la sintaxis.
Los elementos destacados no deben depender únicamente del color para transmitir un significado. Si un elemento destacado indica un error, vincúlalo con otro indicador visual, como text-decoration: wavy underline o una etiqueta de texto adyacente. Asegúrate de que haya suficiente contraste entre el fondo destacado y el color del texto para cumplir con los requisitos de las WCAG 2.1 (al menos 4.5:1 para el texto normal).
Estrategias de resguardo
En el caso de los navegadores que no admiten la API de CSS Custom Highlight, debes proporcionar una experiencia básica funcional en la que el texto siga siendo legible, incluso sin el destacado visual.
Puedes detectar la compatibilidad antes de usar la API:
if (CSS.highlights) {
// CSS Custom Highlight API is supported.
} else {
// Fallback: wrap matches in <mark> elements.
}
Si el destacado es fundamental para la experiencia del usuario, recurre a unir el texto coincidente en elementos <mark>. Esto modifica el DOM, por lo que debes tener cuidado de conservar los objetos de escucha de eventos y evitar romper la estructura 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);
}
}