醉美水芙蓉 发表于 7 天前

马黑黑老师使用CSS Custom Highlight API做全文多彩字

<span class="atips_close" onclick="this.parentNode.style.display='none'">x</span>
</div>
<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>

夕阳西下 发表于 6 天前

谢谢分享!学习了!

夕阳西下 发表于 6 天前

<style>
#mywz {padding:20px; font:bold 26px sans-serif; line-height:1.8; text-align:center; margin:30px auto; width:700px; height:500px; background:lightblue url('') no-repeat center/cover; box-shadow:3px 3px 6px gray; z-index:1; position:relative; border-radius:32px; padding:50px 15px 50px 40px;color: #00008B; }
#mywz > * { color:#222; }
::highlight(l1){color:#f53855;}
::highlight(l2){color:#ff9500;}
::highlight(l3){color:#00b96b;}
::highlight(l4){color:#0088ff;}
::highlight(l5){color:#9945ff;}
::highlight(l6){color:#d628b5;}
::highlight(l7){color:#00aaaa;}
::highlight(l8){color:#c98820;}
</style>
</head>
<body>
<div id="mywz">
<h1>渡荆门送别</h1>
<p>李白</p>
<p>渡远荆门外,来从楚国游。</p>
<p>山随平野尽,江入大荒流。</p>
<p>月下飞天镜,云生结海楼。</p>
<p>仍怜故乡水,万里送行舟。</p>
</div>

<script>
const wrap = document.getElementById('mywz');
const clsArr = ['l1','l2','l3','l4','l5','l6','l7','l8'];
const highList = clsArr.map(name=>{
let hl = new Highlight();
CSS.highlights.set(name, hl);
return hl;
});
const lines = [...wrap.querySelectorAll('h1,p')];
let curIndex = 0;
let timer = null;
const delay = 800;


function clearAll(){
highList.forEach(h=>h.clear());
}

function renderLine(el){
clearAll();
let txtArr = [];
let walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
let n;
while(n=walk.nextNode()) txtArr.push(n);
txtArr.forEach(node=>{
let s = node.textContent;
for(let i=0;i<s.length;i++){
let r = new Range();
r.setStart(node,i);
r.setEnd(node,i+1);
let ran = Math.floor(Math.random()*highList.length);
highList.add(r);
}
})
}

function autoScan(){
renderLine(lines);
curIndex++;
if(curIndex >= lines.length) curIndex = 0;
}

function startTimer(){
if(!timer) timer = setInterval(autoScan, delay);
}

function stopTimer(){
clearInterval(timer);
timer = null;
}

autoScan();
startTimer();

wrap.addEventListener('mouseenter', stopTimer);
wrap.addEventListener('mouseleave', startTimer);
</script>
页: [1]
查看完整版本: 马黑黑老师使用CSS Custom Highlight API做全文多彩字