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
,取消高亮以後沒法復原(可能有別的辦法,可是考慮成本太高,就不考慮),復原方案參考下面