Ctrf+F相信你们必定不陌生,不少人都依赖Ctrf+F来搜索网页上想要看到的内容,也是最频繁使用的功能之一。浏览器之间存在兼容性问题,可是确定都提供原生搜索框(并且快捷键都是ctrf+f)javascript
这里简单介绍下原生搜索框提供了哪些功能html
功能简单且强大,只要是页面内渲染出来的文本都能搜索且定位到目标java
ctrf+f的功能知足咱们平常所需,那么是否是存在不足之处呢?来看看几个案例node
2个以上div标签组成的连续文本git
<div style="display:flex"> <div>搜索</div> <div>测试</div> </div>
手风琴组件被折叠的文本也会搜索出来github
<div>搜索</div> <div style="height: 0px;">测试</div>
针对不足点1,其实document.querySelector('.box').textContent
打印出来结果是搜索和测试中间有个换行符,因此致使浏览器没法搜索出来。在富文本的场景下,这种结构的html很常见,好比某段文字中插入几个高亮文字来作tooltip解释说明。理想的结果固然是能自动生成连续的文字再搜索数组
针对不足点二、3,也是由于咱们不但愿站内某些内容被索引到,或者说索引到的时候能自动展开,好比手风琴浏览器
须要实现一个相似ctrf+f的搜索功能,而且跟框架无关,那么只有从dom上面入手框架
须要一个标识来识别连续文本,经过深度遍历的方式拼接文本同时记录文本节点所在的位置,主要是为了避免破坏原有dom结构的同时高亮跨标签的文本,好比dom
<div style="display:flex"> <div>搜索</div> <div>测试</div> </div> <!-- 替换成 --> <div style="display:flex"> <div><mark>搜索</mark></div> <!-- mark就是高亮标签 --> <div><mark>测试</mark></div> </div>
深度遍历dom结构的同时须要作几个逻辑
scrollHeight、clientHeight、scrollWidth、clientWidth
判断元素是否存在滚动条修复不合理的文本标签,具体缘由后面解释
<div id="error"> 这是异常的 <span>节点</span> </div> <!-- 替换为 --> <div id="error"> <span>这是异常的</span> <span>节点</span> </div>
替换高亮标签的同时保留原有的文本,便于后续恢复
<div> 搜索测试 </div> <!-- 关键词 搜索,dom结构替换为 --> <div> <mark>搜索</mark> 测试 <template>搜索测试</template> </div>
传入一个class或者id便可,作为后续深度遍历的顶层节点const dom = document.querySelector(classname)
height:0
和display:none
)和黑名单标签formatDom(el, value) { const childList = el.childNodes if (!childList.length || !value.length) return // 无子节点或无查询值,则不进行下列操做 childList.forEach(el => { // 遍历其内子节点 if (el.nodeType === 1 || el.nodeType === 3) { //页面内存在滚动节点的话,须要记录 if (isRealNode(el)) { if(el.scrollHeight > el.clientHeight) { //纵向滚动条 this.overflowYDom.push(el) } if(el.scrollWidth > el.clientWidth) { //横向滚动条 this.overflowXDom.push(el) } } if ( isRealNode(el) && // 若是是元素节点 checkClassName(el, this.blackClassName) && !/(script|style|template)/i.test(el.tagName) ) { // 而且元素标签不是script或style或template等特殊元素 this.formatDom(el, value) // 那么就继续遍历(递归)该元素节点 } else if (el.nodeType === 3) { // 记录关键词中字串出现过的文本节点 for (let j = 0; j < value.length; j++) { if (el.data.indexOf(value[j]) > -1) { const start = this.searchDom.text.length this.searchDom.text = this.searchDom.text + el.parentNode.innerText //拼接文本,便于后续处理跨文本标签匹配 this.searchDom.data[`${start}-${this.searchDom.text.length - 1}`] = el //记录每一个文本节点内容在全文本中起始下标位置 break } } } } }) }
假设原始dom长这样
<div> <h2>跨标签文案</h2> <div class="flex"> <div> 这是一段跨标签 </div> <div> 跨多个标签 </div> <div> 组成的文案 </div> <div> 测试 </div> </div> </div>
搜索关键词:文案测试
上一步已经获取到长文本(搜索区域拼接的所有文本内容)和节点出如今长文本的下标
长文本:跨标签文案组成的文案测试
节点出如今长文本的下标:
{ '0-4': textElement, '5-9': textElement, '10-11': textElement }
注:这里只收集包含关键词字串的节点和文本内容,好比 这是一段跨标签
这个节点就不收集
先找到搜索关键词在长文本中出现的起始位置
searchSubStr(str, subStr) { //str 长文本 //subStr 关键词 let arr = [] let index = str.indexOf(subStr) while (index > -1) { arr.push(index) index = str.indexOf(subStr, index + 1) } return arr } //返回 [8]
代码细节能够参考源码,有几个须要注意的事项
须要修复异常节点,好比
<div id="error"> 这是异常的 <span>节点</span> </div> <!-- 替换为 --> <div id="error"> <span>这是异常的</span> <span>节点</span> </div>
缘由在于这是异常的
这个text节点存在一个兄弟节点 span
,取消高亮以后没法复原(可能有别的办法,可是考虑成本太高,就不考虑),复原方案参考下面