大多数产品都存在搜索框,在搜索结果中高亮搜索词是一种不错的交互方式,能让用户快速筛选搜索结果,提升用户体验,在以前,我们很少会这么做,因为要实现这个功能需要较大的成本。
现在,有了 Custom Highlight API 让我们能轻松实现该功能,不需要多余的依赖,也不需要修改现有组件。
效果
简单例子
<body>Lorem Ipsum.
<script>
let textNode = document.body.firstChild;
let r1 = new Range();
r1.setStart(textNode, 1);
r1.setEnd(textNode, 5);
let r2 = new Range();
r2.setStart(textNode, 3);
r2.setEnd(textNode, 7);
// 这里的 `sample` 是一个 CSS name
// 语法是 https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident
// 它用于伪元素 `::highlight`,可以对他应用一些有限制的样式
CSS.highlights.set("sample", new Highlight(r1, r2));
</script>
<style>
::highlight(sample) { background-color: rgba(0, 0, 255, 0.3); }
</style>
高亮多个 Range 除了在构造 Highlight 传递多个参数外,也可以使用 Highlight.prototype.add 方法。
创建搜索词 Range
在搜索结果中创建所有搜索词的 Range 是一件很复杂的事情,但我们可以用一种简单的方法来适配大部分用例,只要遍历 Text 节点,匹配他们值即可:
const getRanges = (root: Node, text: string) => {
const reg = new RegExp([...text].map((c) => `\\u{${c.codePointAt(0)!.toString(16)}}`).join(''), 'gui');
const ranges: Range[] = [];
const nodes: Node[] = [root];
while (!!nodes.length) {
const node = nodes.pop()!;
switch (node.nodeType) {
case Node.TEXT_NODE:
const matched = node.nodeValue?.matchAll(reg);
if (matched) {
for (const arr of matched) {
if (arr.index !== undefined) {
const range = new Range();
range.setStart(node, arr.index);
range.setEnd(node, arr.index + text.length);
ranges.push(range);
}
}
}
break;
case Node.ELEMENT_NODE:
if ((node as Element).shadowRoot) nodes.push((node as Element).shadowRoot as Node);
break;
}
if (node.childNodes[0]) nodes.push(node.childNodes[0]);
if (node.nextSibling) nodes.push(node.nextSibling);
}
return ranges;
};
高亮搜索词
当搜索词改变时,执行高亮即可:
if (!search) return CSS.highlights.clear();
CSS.highlights.set("sample", new Highlight(...getRnages(tbody, search)));
Gem 例子
在不同的框架中,执行高亮有不同的方式,在 gem 中这样以副作用这样调用:
this.effect(
([search]) => {
const tbody = document.querySelector('tbody');
if (!search) return CSS.highlights.clear();
CSS.highlights.set("sample", new Highlight(...getRnages(tbody, search)));
},
() => [this.search],
);
完结 🎉🎉🎉🎉🎉🎉