原生 CSS Custom Highlight 终于来了
介绍一个比较前沿但是非常有用的新特性:一个浏览器原生支持的 CSS
文本高亮功能,官方名称叫做 CSS Custom Highlight API[1],有了它,可以在不改变 dom
结构的情况下自定义任意文本的样式,例如
image-20230210141449363
再例如搜索词高亮
image-20230210200730128
还可以轻易实现代码高亮
image-20230210200832167
多么令人兴奋的功能啊,现在在 Chrome 105
中已经正式支持了(无需开启实验特性),一起学习一下吧
一、伪元素 ::highlight()
要自定义任意文本样式需要 CSS
和 JS
的共同作用。
首先来看 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[2]对象创建文本选择范围,就像用鼠标滑过选区一样,这也是最复杂的一部分,例如
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);
这样可以得到选区对象range1
、range2
2. 创建高亮
然后,将创建的选区高亮实例化,需要用到Highlight[3]对象
const highlight = new Highlight(range1, range2, ...);
当然也可以根据需求创建多个
const highlight1 = new Highlight(user1Range1, user1Range2);
const highlight2 = new Highlight(user2Range1, user2Range2, user2Range3);
这样可以得到高亮对象highlight1
、highlight2
3. 注册高亮
接着,需要将实例化的高亮对象通过[CSS.Highlight](HighlightRegistry - Web APIs | MDN (mozilla.org "CSS.Highlight"))注册到页面
有点类似于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 中的“光标”和“选区”,下面用一张图总结一下
image-20230209193416579
原理就是这样,下面看一些实例
三、彩虹文本
现在来实现文章开头图示效果,彩虹文本效果。总共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; }
现在肯定不会有什么变化,因为还没创建选区
image-20230209200130823
先创建一个高亮区域试试,比如第一个文字
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);
}
效果如下
image-20230209200616748
下面通过循环,创建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
的情况下实现了彩虹文字效果
image-20230209193949265
完整代码可以查看以下任意链接:(注意需要Chrome 105+)
CSS Custom Highlight API (juejin.cn)[4]
CSS Custom Highlight API (codepen.io)[5]
CSS Custom Highlight API (runjs.work)[6]
四、文本搜索高亮
大家都知道浏览器的搜索功能,ctrl+f
就可以快速对整个网页就行查找,查找到的关键词会添加黄色背景的高亮,如下
image-20230210142509747
以前一直很疑惑这个颜色是怎么添加的,毕竟没有任何包裹标签。现在有了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>
简单美化一下后效果如下
image-20230210143359875
然后就是监听输入框,遍历文本节点(推荐使用原生的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.highlightsif (!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;
}
实时搜索效果如下
Kapture 2023-02-10 at 14.51.51
完整代码可以查看以下任意链接:(注意需要Chrome 105+)
CSS Highlight search (juejin.cn)[7]
CSS Highlight search (codepen.io)[8]
CSS Highlight search (runjs.work)[9]
还可以将高亮效果改成波浪线
::highlight(search-results) {text-decoration: underline wavy #f06;
}
效果如下,是不是也可用作错别字标识呢?
image-20230210145628936
除了避免dom
操作带来的便利外,性能也能得到极大的提升,毕竟创建、移除dom
也是性能大户,下面是一个测试 demo,搬运自
https://ffiori.github.io/highlight-api-demos/demo-performance.html[10]
测试代码可以查看以下任意链接:
Highlight performance demo (juejin.cn)[11]
Highlight performance demo (codepen.io)[12]
Highlight performance demo (runjs.work)[13]
测试效果如下
image-20230210152018214
在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; /* 读写纯文本 */
}
效果如下
image-20230210191607226
那么,如何让这些代码高亮呢?
这就需要对内容进行关键词分析提取了,我们可以用现有的代码高亮库,比如highlight.js[14]。
hljs.highlight(pre.textContent, {language: 'css'})._emitter.rootNode.children
通过这个方法可以获取到CSS
语言的关键词以及类型,如下
image-20230210194630601
简单解释一下,这是一个数组,如果是纯文本,表示普通的字符,如果是对象,表示是关键词,例如第一个,children
里面的ul
就是关键词,类型是selector-tag
,也就是选择器,除此之外,还有attribute
、number
、selector-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.scopeconst 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;}
这样就得到了一个支持代码高亮的简易编辑器了
image-20230210191251317
相比传统的编辑器而言,这个属于纯文本编辑,非常轻量,在高亮的同时也不会影响光标,因为不会生成新的dom
,性能也是超级棒
原生 CSS Custom Highlight 终于来了相关推荐
- 原生 CSS “杀死” 预处理器 Sass!
[CSDN 编者按]有着世界上最成熟.稳定和强大的专业级 CSS 扩展语言之称的 Sass,是 CSS 预处理器中的一种,其可编程能力较强,支持函数.列表.对象.判断.循环等等,凭借这些优势,其一度成 ...
- 原生CSS,实现点击按钮出现交互弹窗【新手扫盲】
效果图: 实现原理: 将弹窗内容写在一个div里面,设置display属性为none 按钮点击绑定事件,将上述div的display属性改为block HTML代码 <body><p ...
- 原生CSS设置网站主题色—CSS变量赋值
定义CSS变量 在css文件顶部定义css变量,注意必须以--开头,使用:root包括这几个变量 :root {--main-bg-color: #ff7675;--color1: #fbfee9;- ...
- css打印适应纸张_使用原生css+js+html实现打印A4纸张的功能页面
有时候我们需要使用html+css实现打印A4纸张的功能页面,以下代码实现 A4打印页面 /*横向*/ .a4-endwise{ margin: 0 auto; width: 1070px; heig ...
- 什么是云原生?这回终于有人讲明白了
伴随云计算的滚滚浪潮,云原生(CloudNative)的概念应运而生,云原生很火,火得一塌糊涂,都0202年了,如果你还不懂云原生,那真的out了. 大家言必称云原生,却鲜少有人告诉你到底什么是云原生 ...
- web前端:波浪舞动开机动画loading,原生css、js,@keyframes应用
1.less * {margin: 0;padding: 0;//html,body{}body {height: 100%; //高度继承overflow: hidden; //滚动条禁止#wrap ...
- APP技巧:安卓原生跨屏协同终于来了,还能和 Windows「隔空投送」
谷歌在 CES 2022 上宣布了至少 13 种不同的新软件功能,从类似 AirPods 的快速切换到此前承诺过的软件,可以在 Chromebook 上镜像你的 Android 文本应用程序等.这是谷 ...
- 原生css+html制作简易时钟
先上效果图 html代码 <!-- 时钟边框 --><div class="clock"><!-- 分针 --><div class=&q ...
- 原生CSS input样式美化
原生input框美化 input {outline-style: none;border: 1px solid #c0c4cc;border-radius: 5px;width: 100%;heigh ...
最新文章
- TP获取服务器mysql版本
- iOS 从实际出发理解多线程
- SpringMVC学习(二)——快速搭建SpringMVC开发环境(注解方式)
- 自定义类型转换器代码编写
- 第三十四天 how can I 坚持
- 【C语言】(for循环嵌套)找出1000以内的水仙花数
- matlab视频帧间差分,matlab中视频帧间差分
- Symbian开发——Symbian开发知识(转)
- 天猫为海澜之家打造“智慧门店”;东方网力联手电子科技大学,共建人工智能联合实验室...
- centos 安装Times New Roman
- SNAP Java API处理Sentinel-1数据
- Solana中的account
- android adapter 组件,Android UI - AdapterView 及其子类
- LTE 系统信息SI
- 在Github新建项目
- flask项目实战记录一:搭建flask框架
- JIRA的使用介绍(一)- 概念篇(笔记)
- sp包—bbox函数
- 电脑上玩手机游戏,效果秒杀模拟器
- Knockout监控属性