原生 CSS Custom Highlight 終於來了
介紹一個比較前沿但是非常有用的新特性:一個瀏覽器原生支持的 CSS
文本高亮高亮功能,官方名稱叫做 CSS Custom Highlight API[1],有了它,可以在不改變 dom
結構的情況下自定義任意文本的樣式,例如
再例如搜索詞高亮
還可以輕易實現代碼高亮
多麼令人興奮的功能啊,現在在 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
中需要的 “參數”
- 自定義樣式
最後,將定義的高亮名稱結合::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 (juejin.cn)[4]
-
CSS Custom Highlight API (codepen.io)[5]
-
CSS Custom Highlight API (runjs.work)[6]
四、文本搜索高亮
大家都知道瀏覽器的搜索功能,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 (juejin.cn)[7]
-
CSS Highlight search (codepen.io)[8]
-
CSS Highlight search (runjs.work)[9]
還可以將高亮效果改成波浪線
::highlight(search-results) {
text-decoration: underline wavy #f06;
}
效果如下,是不是也可用作錯別字標識呢?
除了避免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]
測試效果如下
在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[14]。
hljs.highlight(pre.textContent, {
language: 'css'
})._emitter.rootNode.children
通過這個方法可以獲取到CSS
語言的關鍵詞以及類型,如下
簡單解釋一下,這是一個數組,如果是純文本,表示普通的字符,如果是對象,表示是關鍵詞,例如第一個,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.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 (juejin.cn)[15]
-
CSS highlight editor (codepen.io)[16]
-
CSS highlight editor (runjs.work)[17]
六、最後總結一下
以上就是關於CSS Custom Highlight API
的使用方式以及應用示例了,下面再來回顧一下使用步驟:
-
創建選區,
new Range
-
創建高亮,
new Highlight
-
註冊高亮,
CSS.highlights.set
-
自定義樣式,
::highlight()
相比傳統使用標籤的方式而已,有很多優點
-
使用場景更廣泛,很多情況下不能修改
dom
或者成本極大 -
性能更高,避免了操作
dom
帶來的額外開銷,在dom
較多情況下性能差異至少100
倍 -
幾乎沒有副作用,能有效減少
dom
變化引起的其他影響,比如光標選區的處理
其實歸根結底,都是dom
變化帶來的,而Highlight API
恰好能有效避開這個問題。當然也有一些缺陷,由於僅僅能改變文本相關樣式,所以也存在一些侷限性,這個就需要權衡了,目前兼容性也還不足,僅適用於內部項目,敬請期待
參考資料
[1]
CSS Custom Highlight API: https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API
[2]
Range: https://developer.mozilla.org/en-US/docs/Web/API/Range
[3]
Highlight: https://developer.mozilla.org/en-US/docs/Web/API/Highlight
[4]
CSS Custom Highlight API (juejin.cn): https://code.juejin.cn/pen/7198496899391815736
[5]
CSS Custom Highlight API (codepen.io): https://codepen.io/xboxyan/pen/qByzGYr
[6]
CSS Custom Highlight API (runjs.work): https://runjs.work/projects/450431c8f0064298
[7]
CSS Highlight search (juejin.cn): https://code.juejin.cn/pen/7198488612801871929
[8]
CSS Highlight search (codepen.io): https://codepen.io/xboxyan/pen/eYjwoqo
[9]
CSS Highlight search (runjs.work): https://runjs.work/projects/a661feba3dad44c9
[10]
https://ffiori.github.io/highlight-api-demos/demo-performance.html: https://ffiori.github.io/highlight-api-demos/demo-performance.html
[11]
Highlight performance demo (juejin.cn): https://code.juejin.cn/pen/7198487962978353208
[12]
Highlight performance demo (codepen.io): https://codepen.io/xboxyan/pen/YzjoMmp
[13]
Highlight performance demo (runjs.work): https://runjs.work/projects/e5fe09f70d324d99
[14]
highlight.js: https://highlightjs.org/
[15]
CSS highlight editor (juejin.cn): https://code.juejin.cn/pen/7198487629262749756
[16]
CSS highlight editor (codepen.io): https://codepen.io/xboxyan/pen/RwBzOmK
[17]
CSS highlight editor (runjs.work): https://runjs.work/projects/9ff7ab8f12844ce1
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/XMvPKXcqSp0vZ7SPDtQjlA