之前在做关键词检索高亮功能的时候,研究了下目前前端实现高亮的几种方式,第一就是替换dom
元素实现高亮,第二就是利用浏览器新特性Css.highlights
结合js
选区与光标与CSS
高亮伪类实现,实现功能如下:
一、页面布局
一个搜索框,其余要检索关键词的文字,并且给搜索框添加change
事件
二、实现选区
利用TreeWalker
遍历遍历DOM
结构,通过nextNode()
方法,拿到所有文本节点数据,保存到数组里,为接下来的搜索做铺垫
let wrap = document.querySelector('.content')
// 创建 createTreeWalker 迭代器,用于遍历文本节点,保存到一个数组
const treeWalker = document.createTreeWalker(wrap, NodeFilter.SHOW_TEXT);
const allTextNodes = [];
let currentNode = treeWalker.nextNode();
while (currentNode) {
allTextNodes.push(currentNode);
currentNode = treeWalker.nextNode();
}
可以看到,到这一步,拿到所有文本节点数据
接下来,我们根据搜索的结果,拿到匹配到的dom
节点内容的位置信息这里处理下英文小写,这里要注意,可能每句话会出现多次关键词,这里的indexOf
要拼开始检索的参数,开始检索的参数这里记录下,也就是上一个检索关键词的结尾
let search = document.getElementById('search')
search.onchange = event => {
let value = event.target.value // 获取关键词
let indices = []; // 位置信息
const ranges = allTextNodes
.map((el) => {
// 英文全部小写
return { el, text: el.textContent.toLowerCase() };
})
.map(({ text, el }) => {
// 拿到匹配到dom节点的位置信息,并且保存为数组
while (startPos < text.length) {
let startPos = 0
while (startPos < text.length) {
const index = text.indexOf(value, startPos);
if (index === -1) break
indices.push({
el,
start: index,
end: index + value.length
});
startPos = index + value.length;
}
}
});
}
我们可以看下位置信息结果
接下来,创建选区,进行高亮,这里用到了Highlight
,用法如下:Highlight(range1, range2, range3)
// 根据搜索词的位置创建选区
let result = indices.map(item => {
let range = new Range();
range.setStart(item.el, item.start);
range.setEnd(item.el, item.end);
return range
});
// 创建高亮对象
const searchResultsHighlight = new Highlight(...result);
// 注册高亮
CSS.highlights.set("search-results", searchResultsHighlight);
添加css highlight
样式:
.content .item::highlight(search-results) {
background-color: #ffff00;
color: #000;
}
可以看下效果,基本成型,但是当我们切换的时候,上一次的检索结果还在,需要清除高亮记录
// 清除上个高亮
CSS.highlights.clear();
三、完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CSS高亮</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100vw;
height: 100vh;
padding-top: 20px;
}
.search {
width: 100%;
padding: 10px 0;
display: flex;
align-items: center;
justify-content: center;
}
.search input {
width: 500px;
height: 35px;
border: 1px #999 solid;
outline: 0;
background-color: #fff;
border-radius: 3px;
padding-left: 10px;
}
.content {
width: 100%;
padding: 30px;
}
.content .item {
width: 100%;
font-size: 16px;
color: #000;
margin-bottom: 10px;
line-height: 30px;
user-select: none;
letter-spacing: 0.5px;
}
.content .item::highlight(search-results) {
background-color: #ffff00;
color: #000;
}
</style>
</head>
<body>
<div class="container">
<div class="search">
<input id="search" type="text" placeholder="输入关键词检索" />
</div>
<div class="content">
<div class="item">
1、朱自清《荷塘月色》片段
路上只我一个人,背着手踱着。这一片天地好像是我的;我也像超出了平常旳自己,到了另一世界里。我爱热闹,也爱冷静;爱群居,也爱独处。像今晚上,一个人在这苍茫旳月下,什么都可以想,什么都可以不想,便觉是个自由的人。白天里一定要做的事,一定要说的话,现在都可不理。这是独处的妙处,我且受用这无边的荷香月色好了。
曲曲折折的荷塘上面,弥望旳是田田的叶子。叶子出水很高,像亭亭旳舞女旳裙。层层的叶子中间,零星地点缀着些白花,有袅娜(niǎo,nuó)地开着旳,有羞涩地打着朵儿旳;正如一粒粒的明珠,又如碧天里的星星,又如刚出浴的美人。微风过处,送来缕缕清香,仿佛远处高楼上渺茫的歌声似的。这时候叶子与花也有一丝的颤动,像闪电般,霎时传过荷塘的那边去了。叶子本是肩并肩密密地挨着,这便宛然有了一道凝碧的波痕。叶子底下是脉脉(mò)的流水,遮住了,不能见一些颜色;而叶子却更见风致了。
月光如流水一般,静静地泻在这一片叶子和花上。薄薄的青雾浮起在荷塘里。叶子和花仿佛在牛乳中洗过一样;又像笼着轻纱的梦。虽然是满月,天上却有一层淡淡的云,所以不能朗照;但我以为这恰是到了好处——酣眠固不可少,小睡也别有风味的。月光是隔了树照过来的,高处丛生的灌木,落下参差的斑驳的黑影,峭楞楞如鬼一般;弯弯的杨柳的稀疏的倩影,却又像是画在荷叶上。塘中的月色并不均匀;但光与影有着和谐的旋律,如梵婀(ē)玲(英语violin小提琴的译音)上奏着的名曲。
荷塘的四面,远远近近,高高低低都是树,而杨柳最多。这些树将一片荷塘重重围住;只在小路一旁,漏着几段空隙,像是特为月光留下的。树色一例是阴阴的,乍看像一团烟雾;但杨柳的丰姿,便在烟雾里也辨得出。树梢上隐隐约约的是一带远山,只有些大意罢了。树缝里也漏着一两点路灯光,没精打采的,是渴睡人的眼。这时候最热闹的,要数树上的蝉声与水里的蛙声;但热闹是它们的,我什么也没有。
</div>
<div class="item">
2、鲁迅《从百草园到三味书屋》片段
不必说碧绿的菜畦,光滑的石井栏,高大的皂荚树,紫红的桑椹;也不必说鸣蝉在树叶里长吟,肥胖的黄蜂伏在菜花上,轻捷的叫天子(云雀)忽然从草间直窜向云霄里去了。单是周围的短短的泥墙根一带,就有无限趣味。油蛉在这里低唱,蟋蟀们在这里弹琴。翻开断砖来,有时会遇见蜈蚣;还有斑蝥,倘若用手指按住它的脊梁,便会拍的一声,从后窍喷出一阵烟雾。何首乌藤和木莲藤缠络着,木莲有莲房一般的果实,何首乌有拥肿的根。有人说,何首乌根是有象人形的,吃了便可以成仙,我于是常常拔它起来,牵连不断地拔起来,也曾因此弄坏了泥墙,却从来没有见过有一块根象人样。如果不怕刺,还可以摘到覆盆子,象小珊瑚珠攒成的小球,又酸又甜,色味都比桑椹要好得远。
</div>
<div class="item">
3、陈从周《说园》片段
园有静观、动观之分,这一点我们在造园之先,首要考虑。何谓静观,就是园中予游者多驻足的观赏点;动观就是要有较长的游览线。二者说来,小园应以静观为主,动观为辅,庭院专主静观。大园则以动观为主,静观为辅。前者如苏州网师园,后者则苏州拙政园差可似之。人们进入网师园宜坐宜留之建筑多,绕池一周,有槛前细数游鱼,有亭中待月迎风,而轩外花影移墙,峰峦当窗,宛然如画,静中生趣。至于拙政园径缘池转,廊引人随,与“日午画船桥下过,衣香人影太匆匆”的瘦西湖相仿佛,妙在移步换影,这是动观。立意在先,文循意出。动静之分,有关园林性质与园林面积大小。象上海正在建造的盆景园,则宜以静观为主,即为一例。
中国园林是由建筑、山水、花木等组合而成的一个综合艺术品,富有诗情画意。叠山理水要造成“虽由人作,宛自天开”的境界。山与水的关系究竟如何呢?简言之,模山范水,用局部之景而非缩小(网师园水池仿虎丘白莲池,极妙),处理原则悉符画本。山贵有脉,水贵有源,脉源贯通,全园生动。我曾经用“水随山转,山因水活”与“溪水因山成曲折,山蹊随地作低平”来说明山水之间的关系,也就是从真山真水中所得到的启示。明末清初叠山家张南垣主张用平冈小陂、陵阜陂阪,也就是要使园林山水接近自然。如果我们能初步理解这个道理,就不至于离自然太远,多少能呈现水石交融的美妙境界。
</div>
<div class="item">
4、梁实秋《雅舍》片段
“雅舍”最宜月夜——地势较高,得月较先。看山头吐月,红盘乍涌,一霎间,清光四射,天空皎洁,四野无声,微闻犬吠,坐客无不悄然!舍前有两株梨树,等到月升中天,清光从树间筛洒而下,地下阴影斑斓,此时尤为幽绝。直到兴阑人散,归房就寝,月光仍然逼进窗来,助我凄凉。细雨蒙蒙之际,“雅舍”亦复有趣。推窗展望,俨然米氏章法,若云若雾,一片弥漫。但若大雨滂沱,我就又惶悚不安了,屋顶浓印到处都有,起初如碗大,俄而扩大如盆,继则滴水乃不绝,终乃屋顶灰泥突然崩裂,如奇葩初绽,砉然一声而泥水下注,此刻满室狼藉,抢救无及。此种经验,已数见不鲜。
</div>
<div class="item">
5、冰心《图画》
信步走下山门去,何曾想寻幽访胜?
转过山坳来,一片青草地,参天的树影无际。树后弯弯的石桥,桥后两个俯蹲在残照里的狮子。回过头来,只一道的断瓦颓垣,剥落的红门,却深深掩闭。原来是故家陵阙!何用来感慨兴亡,且印下一幅图画。
半山里,凭高下视,千百的燕子,绕着殿儿飞。城垛般的围墙,白石的甬道,黄绿琉璃瓦的门楼,玲珑剔透。楼前是山上的晚霞鲜红,楼后是天边的平原村树,深蓝浓紫。暮霭里,融合在一起。难道是玉宇琼楼?难道是瑶宫贝阙?何用来搜索诗肠,且印下一幅图画。
低头走着,—首诗的断句,忽然浮上脑海来。“四月江南无矮树,人家都在绿阴中。”何用苦忆是谁的著作,何用苦忆这诗的全文。只此已描画尽了山下的人家!
</div>
<div class="item">
6、徐志摩《我所知道的康桥》片段
康桥的灵性全在一条河上;康河,我敢说是全世界最秀丽的一条水。河的名字是葛兰大(Granta),也有叫康河(Kiver Cam)的,许有上下流的区别,我不甚清楚。河身多的是曲折,上游是有名的拜伦潭——“Byron’s Pool”——当年拜伦常在那里玩的;有一个老村子叫格兰骞斯德,有一个果子园,你可以躺在累累的桃李树荫下吃茶,花果会掉入你的茶杯,小雀子会到你桌上来啄食,那真是别有一番天地。这是上游;下游是从骞斯德顿下去,河面展开,那是春夏间竞舟的场所。上下河分界处有一个坝筑,水流急得很,在星光下听水声,听近村晚钟声,听河畔倦牛刍草声,是我康桥经验中最神秘的一种:大自然的优美、宁静,调谐在这星光与波光的默契中不期然的淹入了你的性灵。
</div>
</div>
</div>
</body>
<script>
let wrap = document.querySelector('.content')
// 创建 createTreeWalker 迭代器,用于遍历文本节点,保存到一个数组
const treeWalker = document.createTreeWalker(wrap, NodeFilter.SHOW_TEXT);
const allTextNodes = [];
let currentNode = treeWalker.nextNode();
while (currentNode) {
allTextNodes.push(currentNode);
currentNode = treeWalker.nextNode();
}
let search = document.getElementById('search')
search.onchange = event => {
let value = event.target.value
// 清除上个高亮
CSS.highlights.clear();
if (!value) {
return
}
let indices = [] // 位置信息
const ranges = allTextNodes
.map((el) => {
return { el, text: el.textContent.toLowerCase() };
})
.map(({ text, el }) => {
let startPos = 0
while (startPos < text.length) {
const index = text.indexOf(value, startPos);
if (index === -1) break
indices.push({
el,
start: index,
end: index + value.length
});
startPos = index + value.length;
}
});
// 根据搜索词的位置创建选区
let result = indices.map(item => {
let range = new Range();
range.setStart(item.el, item.start);
range.setEnd(item.el, item.end);
return range
});
// 创建高亮对象
const searchResultsHighlight = new Highlight(...result);
// 注册高亮
CSS.highlights.set("search-results", searchResultsHighlight);
}
</script>
</html>
四、总结
以上就是关于 CSS Custom Highlight API
的使用方法:
- 1. 获取文字节点
createTreeWalker()
- 2. 创建选区
new Range()
- 3. 创建高亮
new Highlight()
- 4. 自定义样式
::hightlight()
**
highlight()
自定义样式只兼容以下:
文本颜色 `color`
背景颜色 `background-color`
文本修饰 `text-decoration`
文本阴影 `text-shadow`
文本描边 `-webkit-text-stroke`
文本填充 `-webkit-text-fill-color`
以下是兼容性,比较差,目前谷歌只兼容 Chrome 105
以上