欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue實(shí)現(xiàn)搜索并高亮文字的兩種方式總結(jié)

 更新時(shí)間:2023年11月15日 14:38:49   作者:錢(qián)得樂(lè)  
在做文字處理的項(xiàng)目時(shí)經(jīng)常會(huì)遇到搜索文字并高亮的需求,常見(jiàn)的實(shí)現(xiàn)方式有插入標(biāo)簽和貼標(biāo)簽兩種,這兩種方式適用于不同的場(chǎng)景,各有優(yōu)劣,下面我們就來(lái)看看他們的具體實(shí)現(xiàn)吧

在做文字處理的項(xiàng)目時(shí)經(jīng)常會(huì)遇到搜索文字并高亮的需求,常見(jiàn)的實(shí)現(xiàn)方式有插入標(biāo)簽和貼標(biāo)簽兩種。這兩種方式適用于不同的場(chǎng)景,各有優(yōu)劣。為了方便操作,直接起一個(gè)Vue項(xiàng)目,在里面演示。

插入標(biāo)簽的方式

簡(jiǎn)單做一個(gè)布局,handleSearch 中放主要邏輯

<script setup>
import { ref } from 'vue'

const text = ref('豫章故郡,洪都新府。星分翼軫,地接衡廬。襟三江而帶五湖,控蠻荊而引甌越。物華天寶,龍光射牛斗之墟;人杰地靈,徐孺下陳蕃之榻。雄州霧列,俊采星馳。臺(tái)隍枕夷夏之交,賓主盡東南之美。都督閻公之雅望,棨戟遙臨;宇文新州之懿范,襜帷暫駐。十旬休假,勝友如云;千里逢迎,高朋滿(mǎn)座。騰蛟起鳳,孟學(xué)士之詞宗;紫電青霜,王將軍之武庫(kù)。家君作宰,路出名區(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>

補(bǔ)充 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)建一個(gè)正則,然后將內(nèi)容做替換,外面裹上 span 標(biāo)簽并加背景顏色。

對(duì) editor 稍作修改,否則標(biāo)簽渲染不出來(lái)

<div class="editor" v-html="text"></div>

于是就實(shí)現(xiàn)了預(yù)期:

然而在有些業(yè)務(wù)場(chǎng)景中被搜索的區(qū)域會(huì)是 contenteditable 可編輯區(qū)域,如果再使用插入標(biāo)簽的方式會(huì)污染原文,這時(shí)這種方式就行不通了。

貼標(biāo)簽的方式

這種方式需要兩個(gè)前置的知識(shí)儲(chǔ)備,一個(gè)是 Document.createRange() ,該方法用以創(chuàng)建一個(gè)包含節(jié)點(diǎn)與文本節(jié)點(diǎn)的一部分的文檔片段。另一個(gè)是 Range.getBoundingClientRect() ,雖然是一個(gè)實(shí)驗(yàn)中的方法,但是主流瀏覽器基本都支持,該方法會(huì)返回一個(gè) DOMRect 對(duì)象,包含8個(gè)屬性,文檔中有詳細(xì)的介紹,在此就不贅述了。

對(duì)頁(yè)面稍作修改:

<script setup>
import { ref, watch, onMounted } from 'vue'

const text = ref('豫章故郡,洪都新府。星分翼軫,地接衡廬。襟三江而帶五湖,控蠻荊而引甌越。物華天寶,龍光射牛斗之墟;人杰地靈,徐孺下陳蕃之榻。雄州霧列,俊采星馳。臺(tái)隍枕夷夏之交,賓主盡東南之美。都督閻公之雅望,棨戟遙臨;宇文新州之懿范,襜帷暫駐。十旬休假,勝友如云;千里逢迎,高朋滿(mǎn)座。騰蛟起鳳,孟學(xué)士之詞宗;紫電青霜,王將軍之武庫(kù)。家君作宰,路出名區(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>

增加了一個(gè) highlight 框,用來(lái)存放高亮的塊, highlight 數(shù)組用來(lái)存放需要高亮的塊的位置信信息。

補(bǔ)充搜索函數(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)建一個(gè)正則,并獲取文本框的文字節(jié)點(diǎn)。用 exec 來(lái)遍歷原文內(nèi)容,這樣可以實(shí)現(xiàn)全文搜索并得到搜索信息,拿到 index 屬性。此時(shí)就用到了前面提到的 createRange ,在文本結(jié)點(diǎn)根據(jù)起始位置和長(zhǎng)度創(chuàng)建一個(gè)選中區(qū)域,并獲取選中區(qū)域的dom信息,將它們存放到一個(gè)數(shù)組中。此時(shí)可以拿到一個(gè)dom信息的數(shù)組:

可以用這個(gè)數(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>

增加對(duì)應(yīng)的樣式:

.tag {
  position: fixed;
  background: yellow;
}

這里使用 fixed 的原因是得到的距離信息時(shí)是相對(duì)于文檔,而不是父元素。但是這種方式是不可靠的,因?yàn)槔又锌删庉媴^(qū)域是可以滾動(dòng)的,一滾動(dòng)高亮區(qū)域就錯(cuò)位了:

所以還是要采用相對(duì)父元素定位,其實(shí)實(shí)現(xiàn)方式很簡(jiǎn)單,先算出父元素相對(duì)于頁(yè)面的定位,再用剛才得出的距離詳見(jiàn),最后得出高亮標(biāo)簽相對(duì)于父元素的定位。畫(huà)個(gè)簡(jiǎn)單的示意圖:

如圖所示,想得到距離3也就是高亮標(biāo)簽相對(duì)于父元素的距離,就是距離2減去距離1。

const wrapperInfo = ref({})
onMounted(() => {
  wrapperInfo.value = wrapperRef.value.getBoundingClientRect()
})

在mounted狀態(tài)下獲取父元素的信息。

封裝一個(gè)計(jì)算位置信息的函數(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))

這時(shí)就可以把定位改為 position: absolute; 了。

此時(shí)再滾動(dòng)樣式也不會(huì)錯(cuò)亂:

但是當(dāng)被搜索詞在跨行時(shí)會(huì)出現(xiàn)bug:

搜索“星分翼軫“,然而兩行都被高亮了,通過(guò)調(diào)試可以看出它并不會(huì)很智能的分塊返回,所以這段邏輯就需要手動(dòng)去實(shí)現(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)建一個(gè) range ,就可以得到行高。然后根據(jù)行高判斷兩種情況:

if (rangeReact.height === lineHeight) {
  highlight.value.push(calRectInfo(rangeReact))
} else {
  // 多行的情況
}

多行的情況可以用雙指針來(lái)試,還以“星分翼軫”為例,設(shè)置 i = 0; j = 1; ,截取文字得到“星”,計(jì)算高度信息,然后 j++; 得到“星分”,當(dāng)文字為“星分翼”的時(shí)候,行高變?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é)果都要把計(jì)算結(jié)果 pushhighlight 中, 當(dāng)后面的結(jié)果可以覆蓋前面的時(shí)候則再 pop 出來(lái)。 if (j !== 1) 的判斷是因?yàn)榈谝淮谓厝r(shí)不應(yīng)該把以前的結(jié)果也刪除掉。

此時(shí)再進(jìn)行搜索,可以折行顯示了。

但其實(shí)還有不盡如人意的地方,因?yàn)檫@個(gè)框是可編輯區(qū)域,當(dāng)插入新的文字時(shí)高亮框不會(huì)實(shí)時(shí)改變,留在了原地。

此時(shí)我們要監(jiān)聽(tīng)文本框文字的改變

<div class="editor" ref="editorRef" contenteditable @input="handleChange">{{ text }}</div>

當(dāng)文字改變時(shí)重新計(jì)算高亮區(qū)域。

const handleChange = () => {
  text.value = editorRef.value.innerText
}

watch(text, () => {
  highlight.value = []
  handleSearch()
})

這樣當(dāng)輸入文字時(shí),高亮區(qū)域可以實(shí)時(shí)計(jì)算:

以上就是vue實(shí)現(xiàn)搜索并高亮文字的兩種方式總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于vue高亮文字的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue里面使用mui的彈出日期選擇插件實(shí)例

    vue里面使用mui的彈出日期選擇插件實(shí)例

    今天小編就為大家分享一篇vue里面使用mui的彈出日期選擇插件實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09
  • Vue正則表達(dá)式限制input的輸入范圍

    Vue正則表達(dá)式限制input的輸入范圍

    我們有時(shí)需要限制文本框輸入內(nèi)容的類(lèi)型,本節(jié)分享下正則表達(dá)式限制文本框只能輸入數(shù)字、小數(shù)點(diǎn)、英文字母、漢字等代碼,感興趣的朋友跟隨小編一起看看吧
    2023-12-12
  • Vue3中引入scss文件的方法步驟

    Vue3中引入scss文件的方法步驟

    這篇文章主要給大家介紹了關(guān)于Vue3中引入scss文件的方法步驟,在實(shí)際項(xiàng)目中,各種樣式往往有很多重復(fù)的情況,為了能夠使樣式的后續(xù)開(kāi)發(fā)和維護(hù)更加愜意,將這些共同的代碼進(jìn)行命名然后調(diào)用這些變量是一個(gè)很好的選擇,需要的朋友可以參考下
    2023-08-08
  • vue.js項(xiàng)目使用原生js實(shí)現(xiàn)移動(dòng)端的輪播圖

    vue.js項(xiàng)目使用原生js實(shí)現(xiàn)移動(dòng)端的輪播圖

    這篇文章主要為大家介紹了vue.js項(xiàng)目中使用原生js實(shí)現(xiàn)移動(dòng)端的輪播圖,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-04-04
  • vue項(xiàng)目之?dāng)?shù)量占比進(jìn)度條實(shí)現(xiàn)方式

    vue項(xiàng)目之?dāng)?shù)量占比進(jìn)度條實(shí)現(xiàn)方式

    這篇文章主要介紹了vue項(xiàng)目之?dāng)?shù)量占比進(jìn)度條實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • 基于VUE選擇上傳圖片并頁(yè)面顯示(圖片可刪除)

    基于VUE選擇上傳圖片并頁(yè)面顯示(圖片可刪除)

    這篇文章主要為大家詳細(xì)介紹了基于VUE選擇上傳圖片并頁(yè)面顯示,圖片可以刪除的效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • Vue中子組件向父組件傳值$emit、.sync的案例詳解

    Vue中子組件向父組件傳值$emit、.sync的案例詳解

    這篇文章主要介紹了Vue中子組件向父組件傳值$emit、.sync的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-09-09
  • Vue組件的繼承用法示例詳解

    Vue組件的繼承用法示例詳解

    這篇文章主要介紹了Vue組件的繼承用法,本文通過(guò)實(shí)例代碼案例講解給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-08-08
  • vue中的$emit 與$on父子組件與兄弟組件的之間通信方式

    vue中的$emit 與$on父子組件與兄弟組件的之間通信方式

    本文主要對(duì)vue 用$emit 與 $on 來(lái)進(jìn)行組件之間的數(shù)據(jù)傳輸。重點(diǎn)給大家介紹vue中的$emit 與$on父子組件與兄弟組件的之間通信方式,感興趣的朋友一起看看
    2018-05-05
  • Vue自定義v-has指令實(shí)現(xiàn)按鈕權(quán)限判斷

    Vue自定義v-has指令實(shí)現(xiàn)按鈕權(quán)限判斷

    這篇文章主要給大家介紹了關(guān)于Vue自定義v-has指令實(shí)現(xiàn)按鈕權(quán)限判斷的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04

最新評(píng)論