Dengan CSS Custom Highlight API, Anda dapat menata gaya rentang teks arbitrer di halaman tanpa mengubah struktur DOM. Hal ini memungkinkan penandaan hasil penelusuran, pewarnaan sintaksis, kursor pengeditan kolaboratif, atau penanda kesalahan ejaan dan tata bahasa tanpa menggabungkan teks dalam elemen tambahan atau mengandalkan manipulasi innerHTML.
Penerapan inti
Untuk menandai rentang teks, Anda harus mengumpulkan node teks target, membuat objek Range dan Highlight, mendaftarkannya di HighlightRegistry, lalu menata gayanya dengan elemen pseudo ::highlight().
1. Mengumpulkan node teks dan membuat rentang
Gunakan TreeWalker untuk mengumpulkan semua node teks dalam elemen target, lalu buat objek Range yang mengarah ke offset karakter yang ingin Anda tandai.
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);
Men-cache daftar node teks dan hanya mem-build ulang saat konten DOM benar-benar berubah, karena menelusuri hierarki memerlukan banyak resource.
2. Membuat Sorotan dari rentang
Mengelompokkan satu atau beberapa objek Range ke dalam Highlight. Beberapa rentang yang memiliki gaya yang sama termasuk dalam satu sorotan.
const searchHighlight = new Highlight(...matchingRanges);
3. Mendaftarkan sorotan di registry
Daftarkan setiap Highlight dengan nama kustom menggunakan CSS.highlights, yang merupakan HighlightRegistry seperti 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);
Jika beberapa sorotan tumpang-tindih, gunakan properti priority untuk mengontrol urutan tumpukan. Penandaan dengan prioritas lebih tinggi akan ditampilkan di atas.
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. Gaya dengan ::highlight()
Gunakan elemen semu ::highlight() di CSS untuk menata gaya setiap sorotan yang terdaftar berdasarkan nama.
::highlight(search-results) {
background-color: #ffdd00;
color: black;
}
Hanya sekumpulan properti CSS terbatas yang berfungsi di dalam ::highlight(): color, background-color, text-decoration dan longhand-nya, text-shadow, -webkit-text-stroke-color, -webkit-text-fill-color, dan -webkit-text-stroke-width. Properti seperti background-image, font-size, atau padding akan diabaikan.
Aksesibilitas
AVOID: menggunakan penanda kustom sebagai pengganti HTML semantik.
Highlight kustom murni bersifat presentasional dan tidak ditampilkan ke hierarki aksesibilitas. Jika teks yang ditandai relevan secara semantik dengan dokumen (misalnya, bagian yang dipilih pengguna), gunakan <mark>. Gunakan highlight kustom untuk efek sementara yang hanya bersifat visual, seperti hasil penelusuran atau pewarnaan sintaksis.
Sorotan tidak boleh hanya mengandalkan warna untuk menyampaikan makna. Jika penanda menunjukkan error, pasangkan dengan indikator visual lain seperti text-decoration: wavy underline atau label teks yang berdekatan. Pastikan kontras yang memadai antara latar belakang sorotan dan warna teks untuk memenuhi persyaratan WCAG 2.1 (minimal 4,5:1 untuk teks normal).
Strategi penggantian
Untuk browser yang tidak mendukung CSS Custom Highlight API, Anda harus memberikan pengalaman dasar yang fungsional, yaitu teks tetap dapat dibaca, meskipun tanpa sorotan visual.
Anda dapat mendeteksi dukungan sebelum menggunakan API:
if (CSS.highlights) {
// CSS Custom Highlight API is supported.
} else {
// Fallback: wrap matches in <mark> elements.
}
Jika penandaan sangat penting untuk pengalaman pengguna, kembalilah ke penggabungan teks yang cocok dalam elemen <mark>. Tindakan ini akan mengubah DOM, jadi pastikan untuk mempertahankan pemroses peristiwa dan menghindari kerusakan struktur dokumen.
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);
}
}