CSS Custom Highlight API cho phép bạn tạo kiểu cho các dải văn bản tuỳ ý trên một trang mà không cần sửa đổi cấu trúc DOM. Điều này cho phép làm nổi bật kết quả tìm kiếm, tô màu cú pháp, con trỏ chỉnh sửa cộng tác hoặc điểm đánh dấu lỗi chính tả và ngữ pháp mà không cần gói văn bản trong các phần tử bổ sung hoặc dựa vào thao tác innerHTML.
Triển khai cốt lõi
Để làm nổi bật dải văn bản, bạn phải thu thập các nút văn bản mục tiêu, tạo đối tượng Range và Highlight, đăng ký các đối tượng đó trong HighlightRegistry, sau đó tạo kiểu cho các đối tượng đó bằng phần tử giả ::highlight().
1. Thu thập nút văn bản và tạo dải ô
Sử dụng TreeWalker để thu thập tất cả các nút văn bản trong phần tử mục tiêu, sau đó tạo các đối tượng Range trỏ đến độ lệch ký tự mà bạn muốn làm nổi bật.
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);
Lưu danh sách nút văn bản vào bộ nhớ đệm và chỉ tạo lại danh sách này khi nội dung DOM thực sự thay đổi, vì việc duyệt qua cây rất tốn kém.
2. Tạo video khoảnh khắc nổi bật từ các phạm vi
Nhóm một hoặc nhiều đối tượng Range vào một Highlight. Nhiều dải ô có cùng kiểu sẽ thuộc về một điểm đánh dấu duy nhất.
const searchHighlight = new Highlight(...matchingRanges);
3. Đăng ký khoảnh khắc nổi bật trong sổ đăng ký
Đăng ký từng Highlight dưới một tên tuỳ chỉnh bằng CSS.highlights, đây là một HighlightRegistry giống như 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);
Khi nhiều điểm đánh dấu chồng lên nhau, hãy sử dụng thuộc tính priority để kiểm soát thứ tự xếp chồng. Nội dung được đánh dấu có mức độ ưu tiên cao hơn sẽ được tô màu ở trên cùng.
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. Tạo kiểu bằng ::highlight()
Sử dụng phần tử giả ::highlight() trong CSS để tạo kiểu cho từng điểm đánh dấu đã đăng ký theo tên.
::highlight(search-results) {
background-color: #ffdd00;
color: black;
}
Chỉ một số thuộc tính CSS nhất định hoạt động bên trong ::highlight(): color, background-color, text-decoration và các thuộc tính viết tay dài, text-shadow, -webkit-text-stroke-color, -webkit-text-fill-color và -webkit-text-stroke-width. Các thuộc tính như background-image, font-size hoặc padding sẽ bị bỏ qua.
Hỗ trợ tiếp cận
AVOID: sử dụng tính năng đánh dấu tuỳ chỉnh để thay thế cho HTML ngữ nghĩa.
Nội dung đánh dấu tuỳ chỉnh hoàn toàn mang tính trình bày và không hiển thị trong cây hỗ trợ tiếp cận. Nếu văn bản được đánh dấu có liên quan về mặt ngữ nghĩa với tài liệu (ví dụ: một đoạn văn do người dùng chọn), hãy sử dụng <mark>. Hãy dành riêng các điểm đánh dấu tuỳ chỉnh cho các hiệu ứng tạm thời, chỉ mang tính hình ảnh như kết quả tìm kiếm hoặc tô màu cú pháp.
Nội dung được làm nổi bật không được chỉ dựa vào màu sắc để truyền tải ý nghĩa. Nếu phần được làm nổi bật cho biết lỗi, hãy ghép nối phần đó với một chỉ báo hình ảnh khác, chẳng hạn như text-decoration: wavy underline hoặc nhãn văn bản liền kề. Đảm bảo độ tương phản đủ giữa nền được đánh dấu và màu văn bản để đáp ứng các yêu cầu của WCAG 2.1 (ít nhất là 4,5:1 đối với văn bản thông thường).
Chiến lược dự phòng
Đối với những trình duyệt không hỗ trợ API Đánh dấu tuỳ chỉnh CSS, bạn nên cung cấp trải nghiệm cơ bản, trong đó văn bản vẫn dễ đọc, ngay cả khi không có phần đánh dấu trực quan.
Bạn có thể phát hiện khả năng hỗ trợ trước khi sử dụng API:
if (CSS.highlights) {
// CSS Custom Highlight API is supported.
} else {
// Fallback: wrap matches in <mark> elements.
}
Nếu phần đánh dấu là quan trọng đối với trải nghiệm người dùng, hãy quay lại gói văn bản trùng khớp trong các phần tử <mark>. Thao tác này sẽ sửa đổi DOM, vì vậy, hãy chú ý giữ lại trình nghe sự kiện và tránh làm hỏng cấu trúc tài liệu.
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);
}
}