马黑黑 发表于 2026-6-1 17:55

使用CSS Custom Highlight API做全文多彩字

<style>
    .artBox { font: normal 18px/1.5 sans-serif; overflow: auto; position: relative; }
    .artBox p { margin: 10px 0; }
    .artBox h1, .artBox h2 { margin: 8px 0; }
    .artBox code { background: #f7f4f3; padding: 2px 6px; tab-size: 4; }
    .artBox blockquote { margin: 10px 20px; padding: 2px 15px; border-left: 3px solid skyblue; background: #e7e5e3; }
</style>

<div class="artBox">
    <blockquote>
      <p>
            <strong>关于CSS Custom Highlight API</strong>
      </p>
      <p>CSS 自定义高亮 API 提供了一种机制,可以通过 JavaScript 创建任意文本范围,并使用 CSS 为其着色。</p>
    </blockquote>
    <p>在现代浏览器中,CSS自定义高亮可以在不改变现有DOM结构的情况下加以实现。完成这一功能,我们需要分几步走。</p>
    <p>
      <strong>一、使用CSS
            <code>::highlight()</code> 伪元素设置高亮样式
      </strong>
    </p>
    <p>::highlight 伪元素语法结构是
      <code>::highlight(高亮名称)</code>,高亮名称自定义,可以根据需要创建一个或多个样式,例如:
    </p>
    <div class="codebox">
/* CSS自定义高亮代码 */
::highlight(c1) { color: red; }
::highlight(c2) { color: blue; }
::highlight(c3) { color: green; }
/* ... 更多的设置 */
    </div>
    <p>目前,::highlight 伪元素可以使用的CSS属性并不多,基本上就是前景色、背景色、文本阴影、下划线等。</p>
    <p>
      <strong>二、在JS中将CSS自定义高亮样式注册为可用样式</strong>
    </p>
    <p>这就是注册高亮,本质上就是将第一步创建的各个 ::highlight 样式弄成 CSS Highlight 接口,提供给指定的文本范围使用。</p>
    <div class="codebox">
    let hl_ar = []; // 高亮样式池
['c1', 'c2', 'c3'].forEach(c =&gt; {
    const hl = new Highlight(); // 创建高亮实例
    hl_ar.push(hl); // 高亮实例加入样式池
    CSS.highlights.set(c, hl); // 当前样式注册到高亮接口(绑定高亮)
});
    </div>
    <p>这样,我们拥有了若干个高亮样式接口,都存储在 hl_ar 数组里,我们可以通过索引使用样式池中的任意一种颜色,它将作用于指定的文本范围,例如这样:</p>
    <div class="codebox">
// 假设已经定义了文本范围 range
hl_ar.add(range);
    </div>
    <p>
      <strong>三、逐字高亮文本节点</strong>
    </p>
    <p>CSS Custom Highlight API 自定义高亮作用于文本节点指定的范围。例如下面简单结构的 div 只拥有一个文本节点,我们可以使用 firstChild 直接获取,然后使用第一、二步骤设置和注册的样式给文本逐字着色。以下代码已经集成了前面两个步骤的代码:</p>
    <div class="codebox" data-prev="1">
    &lt;style&gt;
    #mydiv { padding: 20px; font: bold 24px sans-serif; }
    /* CSS自定义高亮代码 */
    ::highlight(c1) { color: red; }
    ::highlight(c2) { color: blue; }
    ::highlight(c3) { color: green; }
&lt;/style&gt;

&lt;div id="mydiv"&gt;Hello World!&lt;/div&gt;

&lt;script&gt;
    let hl_ar = [];
    ['c1','c2', 'c3'].forEach(c =&gt; {
      const hl = new Highlight();
      hl_ar.push(hl);
      CSS.highlights.set(c, hl);
    });
    const txtNode = mydiv.firstChild;
    for (let i = 0; i &lt; txtNode.textContent.length; i ++) {
      const range = new Range(); // 创建实例化文本范围
      range.setStart(txtNode, i); // 范围起始位置
      range.setEnd(txtNode, i + 1); // 范围结束位置
      hl_ar.add(range);
    }
&lt;/script&gt;
    </div>
    <p>
      <strong>四、操作指定元素的所有文本节点</strong>
    </p>
    <p>然而,容器性质的元素(例如 div),其内部结构不可能都如上例那么单纯,它们可能存在各种类型的节点。为此,要高亮容器元素内部的所有文本,我们需要找出全部的文本节点再对每一个文本节点逐字进行高亮显示的相关操作。</p>
    <p>使用 JS 提供的 TreeWalker 对象可以方便地遍历指定元素的所有节点,并且,遍历过程中可以筛选指定类型的节点(例如只要文本节点)。</p>
    <blockquote>
      <p>
            <strong>关于 TreeWalker 对象</strong>
      </p>
      <p>TreeWalker 对象用于表示文档子树中的节点和它们的位置。TreeWalker 可以使用 Document.createTreeWalker() 方法创建。使用过滤器 TreeWalker.filter(NodeFilter)可以极其方便地筛选节点类型。</p>
    </blockquote>
    <p>假设我们有一个 id="mybox" 的 div,现在我们要取出其内所有的文本节点,并存储到预设数组中:</p>
    <div class="codebox">
let allTxtNodes = []; // 文本节点数组
// 创建 TreeWalker 对象实例
const walker = document.createTreeWalker(<txt-red>mybox, NodeFilter.SHOW_TEXT</txt-red>);
let currentNode = walker.nextNode(); // 当前文本节点
// 递归获取+保存所有文本节点
while (currentNode) {
    allTxtNodes.push(currentNode); // 存储文本节点
    currentNode = walker.nextNode(); // 下一个节点
}
    </div>
    <p>每一个文本节点都等效于步骤三中的 mydiv.firstChild 节点,这么一来,全文多彩字效果就可以实现了。看完整的例子:</p>
    <div class="codebox" data-prev="1">
&lt;style&gt;
    #mybox { padding: 20px; font: bold 24px sans-serif; }
    /* CSS自定义高亮代码 */
    ::highlight(c1) { color: red; }
    ::highlight(c2) { color: orange; }
    ::highlight(c3) { color: magenta; }
    ::highlight(c4) { color: green; }
    ::highlight(c5) { color: teal; }
    ::highlight(c6) { color: blue; }
    ::highlight(c7) { color: purple; }
&lt;/style&gt;

&lt;div id="mybox"&gt;
    &lt;h1&gt;渡荆门送别&lt;/h1&gt;
    &lt;p&gt;李白&lt;/p&gt;
    &lt;p&gt;渡远荆门外,来从楚国游。&lt;/p&gt;
    &lt;p&gt;山随平野尽,江入大荒流。&lt;/p&gt;
    &lt;p&gt;月下飞天镜,云生结海楼。&lt;/p&gt;
    &lt;p&gt;仍怜故乡水,万里送行舟。&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
    // 步骤一:注册高亮样式并存入数组
    let hl_ar = [];

    ['c1','c2', 'c3', 'c4', 'c5', 'c6', 'c7'].forEach(c =&gt; {
      const hl = new Highlight();
      hl_ar.push(hl);
      CSS.highlights.set(c, hl); // 注册当前样式
    });

    // 步骤二:获取并保存 id="mybox" 元素全部文本节点
    let allTxtNodes = []; // 文本节点数组

    // 创建 TreeWalker 对象实例
    const walker = document.createTreeWalker(mybox, NodeFilter.SHOW_TEXT);
    let currentNode = walker.nextNode(); // 当前文本节点

    // 递归获取+保存所有文本节点
    while (currentNode) {
      allTxtNodes.push(currentNode); // 存储文本节点
      currentNode = walker.nextNode(); // 下一个节点
    }

    // 步骤三:遍历所有文本节点+着色
    allTxtNodes.forEach(node =&gt; {
      for (let i = 0; i &lt; node.textContent.length; i ++) {
            const range = new Range(); // 创建实例化文本范围
            range.setStart(node, i); // 范围起始位置
            range.setEnd(node, i + 1); // 范围结束位置
            // 取颜色范围内随机数
            const idx = Math.floor(Math.random() * hl_ar.length);
            hl_ar.add(range); // 着色(文本范围绑定高亮样式)
      }
    });
&lt;/script&gt;
    </div>
    <p>
      <strong>五、其它</strong>
    </p>
    <p>Highlight API可以在现代浏览器中实现无污染文本高亮,它不会改变DOM结构的一丝一毫,这是它的优势。自2025年起,Chrome 105+、Edge 105+、Firefox 140+、Opera 91+、Safari 17.2+ 以及对应的移动端版本均已实现对 Highlight API 的完美支持。</p>
</div>

<script>
   function loadLineNumFile() {
       const script = document.createElement('script');
       script.charset = 'utf-8';
       script.src = 'https://638183.freep.cn/638183/web/helight/linenumber-2026.js';
       script.defer = true;
       script.onload = () => addLineNumber();
       document.head.appendChild(script);
   }
   loadLineNumFile();
</script>

红影 发表于 2026-6-1 20:04

貌似英文的以字母也节点,汉字就是以文字为节点呢。看范例好像依次给文字赋色,用完了再轮换一遍,如此循环着。
一个新的知识点,感谢黑黑的教学{:4_187:}

红影 发表于 2026-6-1 20:05

有一个地方没看懂range.setEnd(node, i + 1); // 范围结束位置

这个怎么是结束位置呢?

马黑黑 发表于 2026-6-1 20:08

红影 发表于 2026-6-1 20:04
貌似英文的以字母也节点,汉字就是以文字为节点呢。看范例好像依次给文字赋色,用完了再轮换一遍,如此循环 ...
这是字符:英文以字母为一个字符,汉字以一个汉字为一个字符。

节点不是这么理解。所谓节点,指的是DOM树中的单位,有很多种类型,其中,<p>文本</p> 是一个元素级节点,里面的“文本”两字是文本节点。

马黑黑 发表于 2026-6-1 20:13

红影 发表于 2026-6-1 20:05
有一个地方没看懂range.setEnd(node, i + 1); // 范围结束位置

这个怎么是结束位置呢?
这是设定字符着色的范围:当遍历到文本节点中的第 i 个字符时,i 是该字符的开始位置(比如 i = 0 时,0 是文本范围第一个字的开始位置),i+1 是第一个字符结束的位置(对应于 i=0 时,i+1=1,这时的文本范围是文本节点的第一个字)。

霜染枫丹 发表于 2026-6-1 21:53

马老师晚上好~~

这个帖子我一定好好的学习,争取学会运用,感谢马老师的无私分享,祝您夏安~~{:4_204:}{:4_190:}

马黑黑 发表于 2026-6-1 22:57

霜染枫丹 发表于 2026-6-1 21:53
马老师晚上好~~

这个帖子我一定好好的学习,争取学会运用,感谢马老师的无私分享,祝您夏安~~{ ...

{:4_191:}

杨帆 发表于 2026-6-2 19:46

运用 Highlight API 并不容易呢,谢谢马老师细致解读与仔细示范{:4_190:}

马黑黑 发表于 2026-6-2 19:47

杨帆 发表于 2026-6-2 19:46
运用 Highlight API 并不容易呢,谢谢马老师细致解读与仔细示范

这个确实有些复杂,但一楼的展现,应该认为逻辑清晰,把握好1~3步就一切OK

杨帆 发表于 2026-6-2 19:57

马黑黑 发表于 2026-6-2 19:47
这个确实有些复杂,但一楼的展现,应该认为逻辑清晰,把握好1~3步就一切OK

是,逻辑很清晰,把握好1~3步很重要,“照猫画虎”相对容易{:4_173:}

马黑黑 发表于 2026-6-2 20:17

杨帆 发表于 2026-6-2 19:57
是,逻辑很清晰,把握好1~3步很重要,“照猫画虎”相对容易

本质就是实现原理

杨帆 发表于 2026-6-2 20:28

马黑黑 发表于 2026-6-2 20:17
本质就是实现原理

是,依照原理去实现才抓住了本质{:4_190:}

马黑黑 发表于 2026-6-3 12:05

杨帆 发表于 2026-6-2 20:28
是,依照原理去实现才抓住了本质

{:4_181:}
页: [1]
查看完整版本: 使用CSS Custom Highlight API做全文多彩字