vue實現(xiàn)搜索并高亮文字的兩種方式總結(jié)
在做文字處理的項目時經(jīng)常會遇到搜索文字并高亮的需求,常見的實現(xiàn)方式有插入標簽和貼標簽兩種。這兩種方式適用于不同的場景,各有優(yōu)劣。為了方便操作,直接起一個Vue項目,在里面演示。
插入標簽的方式
簡單做一個布局,handleSearch
中放主要邏輯
<script setup> import { ref } from 'vue' const text = ref('豫章故郡,洪都新府。星分翼軫,地接衡廬。襟三江而帶五湖,控蠻荊而引甌越。物華天寶,龍光射牛斗之墟;人杰地靈,徐孺下陳蕃之榻。雄州霧列,俊采星馳。臺隍枕夷夏之交,賓主盡東南之美。都督閻公之雅望,棨戟遙臨;宇文新州之懿范,襜帷暫駐。十旬休假,勝友如云;千里逢迎,高朋滿座。騰蛟起鳳,孟學(xué)士之詞宗;紫電青霜,王將軍之武庫。家君作宰,路出名區(qū);童子何知,躬逢勝餞。') const search = ref('') const handleSearch = () => { console.log(search.value) } </script> <template> <div class="editor">{{ text }}</div> <input type="text" v-model="search"> <button @click="handleSearch">搜索</button> </template> <style scoped> .editor { width: 200px; height: 200px; border: 1px solid #ddd; overflow: auto; } </style>
補充 handleSearch
的處理邏輯:
const handleSearch = () => { const regExp = new RegExp(search.value, 'g') text.value = text.value.replace(regExp, `<span style="background: yellow;">${search.value}</span>`) }
用輸入框中的內(nèi)容創(chuàng)建一個正則,然后將內(nèi)容做替換,外面裹上 span
標簽并加背景顏色。
對 editor
稍作修改,否則標簽渲染不出來
<div class="editor" v-html="text"></div>
于是就實現(xiàn)了預(yù)期:
然而在有些業(yè)務(wù)場景中被搜索的區(qū)域會是 contenteditable
可編輯區(qū)域,如果再使用插入標簽的方式會污染原文,這時這種方式就行不通了。
貼標簽的方式
這種方式需要兩個前置的知識儲備,一個是 Document.createRange() ,該方法用以創(chuàng)建一個包含節(jié)點與文本節(jié)點的一部分的文檔片段。另一個是 Range.getBoundingClientRect() ,雖然是一個實驗中的方法,但是主流瀏覽器基本都支持,該方法會返回一個 DOMRect 對象,包含8個屬性,文檔中有詳細的介紹,在此就不贅述了。
對頁面稍作修改:
<script setup> import { ref, watch, onMounted } from 'vue' const text = ref('豫章故郡,洪都新府。星分翼軫,地接衡廬。襟三江而帶五湖,控蠻荊而引甌越。物華天寶,龍光射牛斗之墟;人杰地靈,徐孺下陳蕃之榻。雄州霧列,俊采星馳。臺隍枕夷夏之交,賓主盡東南之美。都督閻公之雅望,棨戟遙臨;宇文新州之懿范,襜帷暫駐。十旬休假,勝友如云;千里逢迎,高朋滿座。騰蛟起鳳,孟學(xué)士之詞宗;紫電青霜,王將軍之武庫。家君作宰,路出名區(qū);童子何知,躬逢勝餞。') const search = ref('') const highlight = ref([]) const editorRef = ref(null) const wrapperRef = ref(null) const handleSearch = () => { } </script> <template> <div class="container"> <div class="wrapper" ref="wrapperRef"> <div class="editor" ref="editorRef" contenteditable>{{ text }}</div> <div class="highlight"></div> </div> </div> <input type="text" v-model="search"> <button @click="handleSearch">搜索</button> </template> <style scoped> .container { width: 200px; height: 200px; border: 1px solid #ddd; overflow: auto; } .wrapper { position: relative; } .highlight { position: absolute; width: 100%; height: 100%; left: 0; top: 0; z-index: -1; } </style>
增加了一個 highlight
框,用來存放高亮的塊, highlight
數(shù)組用來存放需要高亮的塊的位置信信息。
補充搜索函數(shù)中的邏輯
const len = search.value.length const regExp = new RegExp(search.value, 'g') const textNode = editorRef.value.firstChild let result = null while (result = regExp.exec(text.value)) { const { index } = result const range = document.createRange() range.setStart(textNode, index) range.setEnd(textNode, index + len) const rangeReact = range.getBoundingClientRect() highlight.value.push(rangeReact) }
將要搜索的詞創(chuàng)建一個正則,并獲取文本框的文字節(jié)點。用 exec
來遍歷原文內(nèi)容,這樣可以實現(xiàn)全文搜索并得到搜索信息,拿到 index
屬性。此時就用到了前面提到的 createRange
,在文本結(jié)點根據(jù)起始位置和長度創(chuàng)建一個選中區(qū)域,并獲取選中區(qū)域的dom信息,將它們存放到一個數(shù)組中。此時可以拿到一個dom信息的數(shù)組:
可以用這個數(shù)組渲染高亮塊:
<div class="highlight"> <span v-for="item in highlight" class="tag" :style="{ left: item.left + 'px', top: item.top + 'px', width: item.width + 'px', height: item.height + 'px' }"></span> </div>
增加對應(yīng)的樣式:
.tag { position: fixed; background: yellow; }
這里使用 fixed
的原因是得到的距離信息時是相對于文檔,而不是父元素。但是這種方式是不可靠的,因為例子中可編輯區(qū)域是可以滾動的,一滾動高亮區(qū)域就錯位了:
所以還是要采用相對父元素定位,其實實現(xiàn)方式很簡單,先算出父元素相對于頁面的定位,再用剛才得出的距離詳見,最后得出高亮標簽相對于父元素的定位。畫個簡單的示意圖:
如圖所示,想得到距離3也就是高亮標簽相對于父元素的距離,就是距離2減去距離1。
const wrapperInfo = ref({}) onMounted(() => { wrapperInfo.value = wrapperRef.value.getBoundingClientRect() })
在mounted狀態(tài)下獲取父元素的信息。
封裝一個計算位置信息的函數(shù),并修改搜索函數(shù),獲取 rangeReact
后增加和修改代碼:
const calRectInfo = (rangeReact) => { let rectInfo = {} rectInfo.width = rangeReact.width rectInfo.height = rangeReact.height rectInfo.left = rangeReact.left - wrapperInfo.value.left rectInfo.top = rangeReact.top - wrapperInfo.value.top return rectInfo }
highlight.value.push(calRectInfo(rangeReact))
這時就可以把定位改為 position: absolute;
了。
此時再滾動樣式也不會錯亂:
但是當(dāng)被搜索詞在跨行時會出現(xiàn)bug:
搜索“星分翼軫“,然而兩行都被高亮了,通過調(diào)試可以看出它并不會很智能的分塊返回,所以這段邏輯就需要手動去實現(xiàn)。
首先是如何知道需要高亮的區(qū)域是多行。從 DOMReact
中得以得到需要高亮的行高,如果知道一行的高度,就可以知道是不是多行了。
const standardRange = document.createRange() standardRange.setStart(textNode, 0) standardRange.setEnd(textNode, 0) const standardRangeReact = standardRange.getBoundingClientRect() const lineHeight = standardRangeReact.height
在空白處創(chuàng)建一個 range
,就可以得到行高。然后根據(jù)行高判斷兩種情況:
if (rangeReact.height === lineHeight) { highlight.value.push(calRectInfo(rangeReact)) } else { // 多行的情況 }
多行的情況可以用雙指針來試,還以“星分翼軫”為例,設(shè)置 i = 0; j = 1;
,截取文字得到“星”,計算高度信息,然后 j++;
得到“星分”,當(dāng)文字為“星分翼”的時候,行高變?yōu)閮尚?,則應(yīng)高亮“星分”。然后將 i
設(shè)置為 j - 1
。繼續(xù)重復(fù)之前的操作。
let i = 0 let j = 1 while (j <= len) { const subRange = document.createRange() subRange.setStart(textNode, result.index + i) subRange.setEnd(textNode, result.index + j) const subRangeReact = subRange.getBoundingClientRect() if (subRangeReact.height === lineHeight) { if (j !== 1) highlight.value.pop() j++ } else { i = j - 1 } highlight.value.push(calRectInfo(subRangeReact)) }
每次不管是否是最終的結(jié)果都要把計算結(jié)果 push
到 highlight
中, 當(dāng)后面的結(jié)果可以覆蓋前面的時候則再 pop
出來。 if (j !== 1)
的判斷是因為第一次截取時不應(yīng)該把以前的結(jié)果也刪除掉。
此時再進行搜索,可以折行顯示了。
但其實還有不盡如人意的地方,因為這個框是可編輯區(qū)域,當(dāng)插入新的文字時高亮框不會實時改變,留在了原地。
此時我們要監(jiān)聽文本框文字的改變
<div class="editor" ref="editorRef" contenteditable @input="handleChange">{{ text }}</div>
當(dāng)文字改變時重新計算高亮區(qū)域。
const handleChange = () => { text.value = editorRef.value.innerText } watch(text, () => { highlight.value = [] handleSearch() })
這樣當(dāng)輸入文字時,高亮區(qū)域可以實時計算:
以上就是vue實現(xiàn)搜索并高亮文字的兩種方式總結(jié)的詳細內(nèi)容,更多關(guān)于vue高亮文字的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue @click與@click.native,及vue事件機制的使用分析
這篇文章主要介紹了vue @click與@click.native,及vue事件機制的使用分析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04vue實現(xiàn)盒子內(nèi)拖動方塊移動的示例代碼
這篇文章主要給大家介紹了如何通過vue實現(xiàn)盒子內(nèi)拖動方塊移動,文章通過代碼示例講解的非常詳細,具有一定的參考價值,感興趣的小伙伴可以參考閱讀本文2023-08-08解決vue create 創(chuàng)建項目只有兩個文件問題
這篇文章主要介紹了解決vue create 創(chuàng)建項目只有兩個文件問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02Vue裝飾器中的vue-property-decorator?和?vux-class使用詳解
這篇文章主要介紹了Vue裝飾器中的vue-property-decorator?和?vux-class使用詳解,通過示例代碼給大家介紹的非常詳細,對vue-property-decorator?和?vux-class的使用感興趣的朋友一起看看吧2022-08-08