关于前端:原生-CSS-Custom-Highlight-终于来了

26次阅读

共计 8210 个字符,预计需要花费 21 分钟才能阅读完成。

欢送关注我的公众号:前端侦探

介绍一个比拟前沿然而十分有用的新个性:一个浏览器原生反对的 CSS 文本高亮高亮性能,官网名称叫做 CSS Custom Highlight API,有了它,能够在不扭转 dom 构造的状况下自定义任意文本的款式,例如

再例如搜索词高亮

还能够轻易实现代码高亮

如许令人兴奋的性能啊,当初在 Chrome 105 中曾经正式反对了(无需开启试验个性),一起学习一下吧

一、伪元素 ::highlight()

要自定义任意文本款式须要 CSSJS 的独特作用。

首先来看 CSS 局部,一个新的伪元素,非常简单

::highlight(custom-highlight-name) {color: red}

::selection 这类伪元素比拟相似,仅反对局部文本相干款式,如下

  • 文本色彩 color
  • 背景色彩 background-color
  • 文本润饰 text-decoration
  • 文本暗影 text-shadow
  • 文本描边 -webkit-text-stroke
  • 文本填充 -webkit-text-fill-color

留神,留神,留神 不反对background-image,也就是突变之类的也不反对

然而,仅仅晓得这个伪类是没用的,她还须要一个 “参数”,也就是下面的custom-highlight-name,示意高亮的名称,那这个是怎么来的呢?或者换句话说,如何去 标识页面中须要自定义款式的那局部文本 呢?

这就须要借助上面的内容了,看看如何生成这个“参数”,这才是重点

二、CSS Custom Highlight API

在介绍之前,倡议先仔细阅读这篇文章:web 中的“光标”和“选区”

大部分操作其实和这个原理是雷同的,只是把拿到的选区做了进一步解决,具体分以下几步

1. 创立选区(重点)

首先,通过 Range 对象创立文本抉择范畴,就像用鼠标滑过选区一样,这也是最简单的一部分,例如

const parentNode = document.getElementById("foo");

const range1 = new Range();
range1.setStart(parentNode, 10);
range1.setEnd(parentNode, 20);

const range2 = new Range();
range2.setStart(parentNode, 40);
range2.setEnd(parentNode, 60);

这样能够失去 选区 对象range1range2

2. 创立高亮

而后,将创立的选区 高亮实例化,须要用到 Highlight 对象

const highlight = new Highlight(range1, range2, ...);

当然也能够依据需要创立多个

const highlight1 = new Highlight(user1Range1, user1Range2);
const highlight2 = new Highlight(user2Range1, user2Range2, user2Range3);

这样能够失去 高亮 对象highlight1highlight2

3. 注册高亮

接着,须要将实例化的高亮对象通过 CSS.Highlight](https://developer.mozilla.org…))注册到页面

有点相似于 Map 对象的操作

CSS.highlights.set("highlight1", highlight1);
CSS.highlights.set("highlight2", highlight2);

目前兼容性比拟差,所以须要额定判断一下

if (CSS.highlights) {//... 反对 CSS.highlights}

留神看,下面注册的 key 名,highlight1就是上一节提到的高亮名称,也就是 CSS 中须要的“参数”

4. 自定义款式

最初,将定义的高亮名称联合::highlight,这样就能够自定义选中款式了

::highlight(highlight1) {
  background-color: yellow;
  color: black;
}

以上就是全副过程了,稍显简单,然而还是比拟好了解的,要害是第一步创立选区的过程,最为简单,再次举荐仔细阅读这篇文章:web 中的“光标”和“选区”,上面用一张图总结一下

原理就是这样,上面看一些实例

三、彩虹文本

当初来实现文章结尾图示成果,彩虹文本成果。总共 7 种颜色,文字顺次变色,一直循环,而且仅有一个标签

<p id="rainbow-text">CSS Custom Highlight API</p>

这里总共有 7 种颜色,所以须要创立 7 个高亮区域,能够先定义高亮 CSS,如下

::highlight(rainbow-color-1) {color: #ad26ad;  text-decoration: underline;}
::highlight(rainbow-color-2) {color: #5d0a99;  text-decoration: underline;}
::highlight(rainbow-color-3) {color: #0000ff;  text-decoration: underline;}
::highlight(rainbow-color-4) {color: #07c607;  text-decoration: underline;}
::highlight(rainbow-color-5) {color: #b3b308;  text-decoration: underline;}
::highlight(rainbow-color-6) {color: #ffa500;  text-decoration: underline;}
::highlight(rainbow-color-7) {color: #ff0000;  text-decoration: underline;}

当初必定不会有什么变动,因为还没创立选区

先创立一个高亮区域试试,比方第一个文字

const textNode = document.getElementById("rainbow-text").firstChild;
if (CSS.highlights) {const range = new Range();
  range.setStart(textNode, 0); // 选区终点
  range.setEnd(textNode, 1); // 选区起点
  const Highlight = new Highlight(range);
  CSS.highlights.set(`rainbow-color-1`, Highlight);
}

成果如下

上面通过循环,创立 7 个高亮区域

const textNode = document.getElementById("rainbow-text").firstChild;

if (CSS.highlights) {const highlights = [];
  for (let i = 0; i < 7; i++) {
    // 给每个色彩实例化一个 Highlight 对象
    const colorHighlight = new Highlight();
    highlights.push(colorHighlight);

    // 注册高亮
    CSS.highlights.set(`rainbow-color-${i + 1}`, colorHighlight);
  }

  // 遍历文本节点
  for (let i = 0; i < textNode.textContent.length; i++) {
    // 给每个字符创立一个选区
    const range = new Range();
    range.setStart(textNode, i);
    range.setEnd(textNode, i + 1);

    // 增加到高亮
    highlights[i % 7].add(range);
  }
}

这样就在不扭转 dom 的状况下实现了彩虹文字效果

残缺代码能够查看以下任意链接:(留神须要 Chrome 105+)

  • CSS Custom Highlight API (codepen.io)
  • CSS Custom Highlight API (runjs.work)

四、文本搜寻高亮

大家都晓得浏览器的搜寻性能,ctrl+f就能够疾速对整个网页就行查找,查找到的关键词会增加黄色背景的高亮,如下

以前始终很纳闷这个色彩是怎么增加的,毕竟没有任何包裹标签。当初有了CSS Custom Highlight API ,齐全能够手动实现一个和原生浏览器截然不同的搜寻高亮性能。

到目前为止,还无奈自定义原生搜寻高亮的黄色背景,当前可能会凋谢

假如 HTML 构造是这样的,一个搜寻框和一堆文本

<label> 搜寻 <input id="query" type="text"></label>
<article>
  <p>
    阅文旗下囊括 QQ 浏览、终点中文网、新丽传媒等业界知名品牌,汇聚了弱小的创作者营垒、丰盛的作品储备,笼罩 200 多种内容品类,触达数亿用户,已胜利输入《庆余年》《赘婿》《鬼吹灯》《全职高手》《斗罗大陆》《琅琊榜》等大量优良网文 IP,改编为动漫、影视、游戏等多业态产品。</p>
  <p>《盗墓笔记》最后连载于终点中文网,是南派三叔成名代表作。2015 年网剧开播首日点击破亿,开启了盗墓文学 IP 年。电影于 2016 年上映,由井柏然、鹿晗、马思纯等主演,累计票房 10 亿元。</p>
  <p>
    庆余年》是阅文团体白金作家猫腻的作品,自 2007 年在终点中文网连载,持续保持历史类珍藏榜前五位。改编剧集成为 2019 年景象级作品,播出期间登上微博热搜百余次,腾讯视频、爱奇艺双平台总播放量冲破 160 亿次,并荣获第 26 届白玉兰奖最佳编剧(改编)、最佳男配角两项大奖。</p>
  <p>《鬼吹灯》是天下霸唱创作的经典悬疑盗墓小说,连载于终点中文网。先后进行过漫画、游戏、电影、网络电视剧的改编,均获得不俗的问题,是当之无愧的超级 IP。</p>
</article>

简略丑化一下后成果如下

而后就是监听输入框,遍历文本节点(举荐应用原生的treeWalker,当然一般的递归也能够),依据搜索词创立选区,具体代码如下

const query = document.getElementById("query");
const article = document.querySelector("article");

// 创立 createTreeWalker 迭代器,用于遍历文本节点,保留到一个数组
const treeWalker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT);
const allTextNodes = [];
let currentNode = treeWalker.nextNode();
while (currentNode) {allTextNodes.push(currentNode);
  currentNode = treeWalker.nextNode();}

// 监听 inpu 事件
query.addEventListener("input", () => {
  // 判断一下是否反对 CSS.highlights
  if (!CSS.highlights) {
    article.textContent = "CSS Custom Highlight API not supported.";
    return;
  }

  // 革除上个高亮
  CSS.highlights.clear();

  // 为空判断
  const str = query.value.trim().toLowerCase();
  if (!str) {return;}

  // 查找所有文本节点是否蕴含搜索词
  const ranges = allTextNodes
    .map((el) => {return { el, text: el.textContent.toLowerCase() };
    })
    .map(({text, el}) => {const indices = [];
      let startPos = 0;
      while (startPos < text.length) {const index = text.indexOf(str, startPos);
        if (index === -1) break;
        indices.push(index);
        startPos = index + str.length;
      }

      // 依据搜索词的地位创立选区
      return indices.map((index) => {const range = new Range();
        range.setStart(el, index);
        range.setEnd(el, index + str.length);
        return range;
      });
    });

  // 创立高亮对象
  const searchResultsHighlight = new Highlight(...ranges.flat());

  // 注册高亮
  CSS.highlights.set("search-results", searchResultsHighlight);
});

最初,通过 CSS 设置高亮的色彩

::highlight(search-results) {
  background-color: #f06;
  color: white;
}

实时搜寻成果如下

残缺代码能够查看以下任意链接:(留神须要 Chrome 105+)

  • CSS Highlight search (codepen.io)
  • CSS Highlight search (runjs.work)

还能够将高亮成果改成波浪线

::highlight(search-results) {text-decoration: underline wavy #f06;}

成果如下,是不是也可用作 错别字标识 呢?

除了防止 dom 操作带来的便当外,性能也能失去极大的晋升 ,毕竟创立、移除dom 也是性能小户,上面是一个测试 demo,搬运自

https://ffiori.github.io/highlight-api-demos/demo-performance.html

测试代码能够查看以下任意链接:

  • Highlight performance demo (codepen.io)
  • Highlight performance demo (runjs.work)

测试成果如下

10000 个节点的状况下,两者相差 100 倍的差距!而且数量越大,性能差距越显著,甚至间接导致浏览器卡死!

五、代码高亮编辑器

最初再来看一个十分实用的例子,能够轻易实现一个代码高亮的编辑器。

假如 HTML构造是这样的,很简略,就一个纯文本的标签

<pre class="editor" id="code">ul{min-height: 0;}
.sub {
  display: grid;
  grid-template-rows: 0fr;
  transition: 0.3s;
  overflow: hidden;
}
:checked ~ .sub {grid-template-rows: 1fr;}
.txt{animation: color .001s .5 linear forwards;}
@keyframes color {
  from {color: var(--c1)
  }
  to{color: var(--c2)
  }
}</pre>

简略润饰一下,设置为可编辑元素

.editor{
  white-space: pre-wrap;
  -webkit-user-modify: read-write-plaintext-only; /* 读写纯文本 */
}

成果如下

那么,如何让这些代码高亮呢?

这就须要对内容进行关键词剖析提取了,咱们能够用现有的代码高亮库,比方 highlight.js。

 hljs.highlight(pre.textContent, {language: 'css'})._emitter.rootNode.children

通过这个办法能够获取到 CSS 语言的关键词以及类型,如下

简略解释一下,这是一个数组,如果是纯文本,示意一般的字符,如果是对象,示意是关键词,例如第一个,children外面的 ul 就是关键词,类型是 selector-tag,也就是 选择器 ,除此之外,还有attributenumberselector-class 等各种类型。有了这些关键词,咱们就能够把这些文本独自选取进去,而后高亮成不同的色彩。

接下来,就须要对代码内容进行遍历了,办法也是相似的,如下

const nodes = pre.firstChild
const text = nodes.textContent
const highlightMap = {}
let startPos = 0;
words.filter(el => el.scope).forEach(el => {const str = el.children[0]
  const scope = el.scope
  const index = text.indexOf(str, startPos);
  if (index < 0) {return}
  const item = {
    start: index,
    scope: scope,
    end: index + str.length,
    str: str
  }
  if (highlightMap[scope]){highlightMap[scope].push(item)
  } else {highlightMap[scope] = [item]
  }
  startPos = index + str.length;
})
Object.entries(highlightMap).forEach(function([k,v]){const ranges = v.map(({start, end}) => {const range = new Range();
    range.setStart(nodes, start);
    range.setEnd(nodes, end);
    return range;
  });
  const highlight = new Highlight(...ranges.flat());
  CSS.highlights.set(k, highlight);
})
}
highlights(code)
code.addEventListener('input', function(){highlights(this)
})

最初,依据不同的类型,定义不同的色彩就行了,如下

::highlight(built_in) {color: #c18401;}
::highlight(comment) {
  color: #a0a1a7;
  font-style: italic;
  }
::highlight(number),
::highlight(selector-class){color: #986801;}
::highlight(attr) {color: #986801;}
::highlight(string) {color: #50a14f;}
::highlight(selector-pseudo) {color: #986801;}
::highlight(attribute) {color: #50a14f;}
::highlight(keyword) {color: #a626a4;}

这样就失去了一个反对代码高亮的繁难编辑器了

相比传统的编辑器而言,这个属于纯文本编辑,十分轻量,在高亮的同时也不会影响光标,因为不会生成新的dom,性能也是超级棒👍🏻

残缺代码能够查看以下任意链接:

  • CSS highlight editor (codepen.io)
  • CSS highlight editor (runjs.work)

六、最初总结一下

以上就是对于 CSS Custom Highlight API 的应用形式以及利用示例了,上面再来回顾一下应用步骤:

  1. 创立选区,new Range
  2. 创立高亮,new Highlight
  3. 注册高亮,CSS.highlights.set
  4. 自定义款式,::highlight()

相比传统应用标签的形式而已,有很多长处

  1. 应用场景更宽泛,很多状况下不能批改 dom 或者老本极大
  2. 性能更好,防止了操作 dom 带来的额定开销,在 dom 较多状况下性能差别至多 100
  3. 简直没有副作用,能无效缩小 dom 变动引起的其余影响,比方光标选区的解决

其实归根结底,都是 dom 变动带来的,而 Highlight API 恰好能无效避开这个问题。当然也有一些缺点,因为仅仅能扭转文本相干款式,所以也存在一些局限性,这个就须要衡量了,目前兼容性也还有余,仅实用于外部我的项目,敬请期待

最初,如果感觉还不错,对你有帮忙的话,欢送 点赞、珍藏、转发❤❤❤

欢送关注我的公众号:前端侦探

正文完
 0