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

介绍一个比拟前沿然而十分有用的新个性:一个浏览器原生反对的 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.firstChildconst text = nodes.textContentconst 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恰好能无效避开这个问题。当然也有一些缺点,因为仅仅能扭转文本相干款式,所以也存在一些局限性,这个就须要衡量了,目前兼容性也还有余,仅实用于外部我的项目,敬请期待

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

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