searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

前端高亮搜索

2024-07-17 09:40:02
4
0

I.粗糙版

首先实现高亮的第一反应是使用html的方式渲染原文本,如 innerHTML、vue的v-html react的dangerouslySetInnerHTML。搜索关键字时如果有匹配项就替换原文本为span标签,并加上高亮样式。于是一个粗糙版的搜索高亮就实现了。

<script setup>
import { ref } from 'vue'
const text = ref(
  '噫吁嚱,危乎高哉!蜀道之难,难于上青天!蚕丛及鱼凫,开国何茫然!尔来四万八千岁,不与秦塞通人烟。西当太白有鸟道,可以横绝峨眉巅。地崩山摧壮士死,然后天梯石栈相钩连。上有六龙回日之高标,下有冲波逆折之回川。黄鹤之飞尚不得过,猿猱欲度愁攀援。青泥何盘盘,百步九折萦岩峦。扪参历井仰胁息,以手抚膺坐长叹。问君西游何时还?畏途巉岩不可攀。但见悲鸟号古木,雄飞雌从绕林间。又闻子规啼夜月,愁空山。蜀道之难,难于上青天,使人听此凋朱颜!连峰去天不盈尺,枯松倒挂倚绝壁。飞湍瀑流争喧豗,砯崖转石万壑雷。其险也如此,嗟尔远道之人胡为乎来哉!剑阁峥嵘而崔嵬,一夫当关,万夫莫开。所守或匪亲,化为狼与豺。朝避猛虎,夕避长蛇;磨牙吮血,杀人如麻。锦城虽云乐,不如早还家。蜀道之难,难于上青天,侧身西望长咨嗟!',
)
const searchText = ref('')

const handleSearch = () => {
  text.value = renderHighLights(text.value, searchText.value)
}

const renderHighLights = (text, keyword) => {
  const regex = new RegExp(keyword, 'gi')
  // 使用正则表达式替换匹配的部分,并添加高亮效果
  return text.replace(
    regex,
    (match) => `<span style="background: yellow;">${match}</span>`,
  )
}
</script>

<template>
  <div>
    <input type="text" v-model="searchText" placeholder="请输入...">
    <button @click="handleSearch">搜索并高亮</button>
    <div ref="content">
      <p v-html="text"></p>
    </div>
  </div>
</template>

WX20240612-161211@2x.png

II.优化一下

1.由于我们是直接替换原文本,那么再次搜索之后就可能会发现匹配不到文本了,这是由于原文本被替换为span标签,正则表达式不能正确匹配。这就需要我们添加一个变量存储被替换后的文本,原文本保持不变,每次搜索从原文本匹配,搜索完成后,修改这个变量的值就可以了。

2.因为使用正则进行匹配,当搜索的字符是一些特殊字符时,就会出现问题,比如输入 .*就会匹配所有字符串。

WX20240612-162717@2x.png

此时我们需要对搜索的关键字进行转义,这样就能避免这个问题了。

const escapeRegExp = (string) => {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

3.在开发中遇到过一个情况:需要搜索个高亮的文本带有换行符,没法实现跨行搜索。

在正则表达式中 \s能匹配所有的空白符,其中也包括换行符,这里将搜索关键词中的每个字符之间插入 \s* 以匹配任何数量的空白字符以实现跨行搜索,当然中间间隔其他空白符我们也能匹配到,这个是否需要看需求。

 const regex = new RegExp(keyword.split('').join('\\s*'), 'gi')

WX20240612-170027@2x.png

至此,一个优化版的搜索高亮就完成了。

<script setup>
import { ref, onMounted, watch } from 'vue'
const text = ref(
  '噫吁嚱,危乎高哉!蜀道 之难,难于上青天!蚕丛及鱼凫,开国何茫然!尔来四万八千岁,不与秦塞通人烟。西当太白有鸟道,可以横绝峨眉巅。地崩山摧壮士死,然后天梯石栈相钩连。上有六龙回日之高标,下有冲波逆折之回川。黄鹤之飞尚不得过,猿猱欲度愁攀援。青泥何盘盘,百步九折萦岩峦。扪参历井仰胁息,以手抚膺坐长叹。问君西游何时还?畏途巉岩不可攀。但见悲鸟号古木,雄飞雌从绕林间。又闻子规啼夜月,愁空山。蜀道之难,难于上青天,使人听此凋朱颜!连峰去天不盈尺,枯松倒挂倚绝壁。飞湍瀑流争喧豗,砯崖转石万壑雷。其险也如此,嗟尔远道之人胡为乎来哉!剑阁峥嵘而崔嵬,一夫当关,万夫莫开。所守或匪亲,化为狼与豺。朝避猛虎,夕避长蛇;磨牙吮血,杀人如麻。锦城虽云乐,不如早还家。蜀道之难,难于上青天,侧身西望长咨嗟!',
)
const searchText = ref('')
const displayText = ref('')

onMounted(() => {
  handleSearch()
})

const handleSearch = () => {
  displayText.value = renderHighLights(text.value, searchText.value)
}

/**
 * @description: 此方法用于转义用户输入的特殊字符 如.*等等 避免影响正则表达式的生成导致匹配错误
 * @param {string} string
 * @return {string} 转义后的字符串
 */
const escapeRegExp = (string) => {
  // 转义正则表达式中的特殊字符
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& 表示整个匹配的子字符串
}

/**
 * @description: 关键词匹配高亮 此方法使用v-html片段渲染 需注意xss攻击
 * @param {string} text 原始文本
 * @param {string} keyword 关键词
 * @return {string} 替换后的html片段
 */
const renderHighLights = (text, keyword) => {
  if (keyword === '' || !text) {
    // 如果关键词为空或文本为空,直接返回原文本
    return text
  }

  // 转义关键词中的特殊字符 防止输入恶意字符
  const escapedKeyword = escapeRegExp(keyword)

  // 构建跨行匹配的正则表达式
  // 将关键词中的每个字符之间插入 \s* 以匹配任何数量的空白字符(包括换行符)
  const regex = new RegExp(escapedKeyword.split('').join('\\s*'), 'gi')
  // 使用正则表达式替换匹配的部分,并添加高亮效果
  return text.replace(
    regex,
    (match) => `<span style="background: yellow;">${match}</span>`,
  )
}

watch(searchText, () => {
  handleSearch()
})
</script>

<template>
  <div>
    <input type="text" v-model="searchText" placeholder="请输入...">
    <button @click="handleSearch">搜索并高亮</button>
    <div class="text" ref="content">
      <pre v-html="displayText"></pre>
    </div>
  </div>
</template>

<style scoped>
.text {
  width: 400px;
  height: 400px;
  padding: 0 10px;
  border: 1px solid #ddd;
  overflow: auto;
  pre {
    white-space: pre-wrap;
  }
  
}
</style>
0条评论
作者已关闭评论
w****n
4文章数
0粉丝数
w****n
4 文章 | 0 粉丝
w****n
4文章数
0粉丝数
w****n
4 文章 | 0 粉丝
原创

前端高亮搜索

2024-07-17 09:40:02
4
0

I.粗糙版

首先实现高亮的第一反应是使用html的方式渲染原文本,如 innerHTML、vue的v-html react的dangerouslySetInnerHTML。搜索关键字时如果有匹配项就替换原文本为span标签,并加上高亮样式。于是一个粗糙版的搜索高亮就实现了。

<script setup>
import { ref } from 'vue'
const text = ref(
  '噫吁嚱,危乎高哉!蜀道之难,难于上青天!蚕丛及鱼凫,开国何茫然!尔来四万八千岁,不与秦塞通人烟。西当太白有鸟道,可以横绝峨眉巅。地崩山摧壮士死,然后天梯石栈相钩连。上有六龙回日之高标,下有冲波逆折之回川。黄鹤之飞尚不得过,猿猱欲度愁攀援。青泥何盘盘,百步九折萦岩峦。扪参历井仰胁息,以手抚膺坐长叹。问君西游何时还?畏途巉岩不可攀。但见悲鸟号古木,雄飞雌从绕林间。又闻子规啼夜月,愁空山。蜀道之难,难于上青天,使人听此凋朱颜!连峰去天不盈尺,枯松倒挂倚绝壁。飞湍瀑流争喧豗,砯崖转石万壑雷。其险也如此,嗟尔远道之人胡为乎来哉!剑阁峥嵘而崔嵬,一夫当关,万夫莫开。所守或匪亲,化为狼与豺。朝避猛虎,夕避长蛇;磨牙吮血,杀人如麻。锦城虽云乐,不如早还家。蜀道之难,难于上青天,侧身西望长咨嗟!',
)
const searchText = ref('')

const handleSearch = () => {
  text.value = renderHighLights(text.value, searchText.value)
}

const renderHighLights = (text, keyword) => {
  const regex = new RegExp(keyword, 'gi')
  // 使用正则表达式替换匹配的部分,并添加高亮效果
  return text.replace(
    regex,
    (match) => `<span style="background: yellow;">${match}</span>`,
  )
}
</script>

<template>
  <div>
    <input type="text" v-model="searchText" placeholder="请输入...">
    <button @click="handleSearch">搜索并高亮</button>
    <div ref="content">
      <p v-html="text"></p>
    </div>
  </div>
</template>

WX20240612-161211@2x.png

II.优化一下

1.由于我们是直接替换原文本,那么再次搜索之后就可能会发现匹配不到文本了,这是由于原文本被替换为span标签,正则表达式不能正确匹配。这就需要我们添加一个变量存储被替换后的文本,原文本保持不变,每次搜索从原文本匹配,搜索完成后,修改这个变量的值就可以了。

2.因为使用正则进行匹配,当搜索的字符是一些特殊字符时,就会出现问题,比如输入 .*就会匹配所有字符串。

WX20240612-162717@2x.png

此时我们需要对搜索的关键字进行转义,这样就能避免这个问题了。

const escapeRegExp = (string) => {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

3.在开发中遇到过一个情况:需要搜索个高亮的文本带有换行符,没法实现跨行搜索。

在正则表达式中 \s能匹配所有的空白符,其中也包括换行符,这里将搜索关键词中的每个字符之间插入 \s* 以匹配任何数量的空白字符以实现跨行搜索,当然中间间隔其他空白符我们也能匹配到,这个是否需要看需求。

 const regex = new RegExp(keyword.split('').join('\\s*'), 'gi')

WX20240612-170027@2x.png

至此,一个优化版的搜索高亮就完成了。

<script setup>
import { ref, onMounted, watch } from 'vue'
const text = ref(
  '噫吁嚱,危乎高哉!蜀道 之难,难于上青天!蚕丛及鱼凫,开国何茫然!尔来四万八千岁,不与秦塞通人烟。西当太白有鸟道,可以横绝峨眉巅。地崩山摧壮士死,然后天梯石栈相钩连。上有六龙回日之高标,下有冲波逆折之回川。黄鹤之飞尚不得过,猿猱欲度愁攀援。青泥何盘盘,百步九折萦岩峦。扪参历井仰胁息,以手抚膺坐长叹。问君西游何时还?畏途巉岩不可攀。但见悲鸟号古木,雄飞雌从绕林间。又闻子规啼夜月,愁空山。蜀道之难,难于上青天,使人听此凋朱颜!连峰去天不盈尺,枯松倒挂倚绝壁。飞湍瀑流争喧豗,砯崖转石万壑雷。其险也如此,嗟尔远道之人胡为乎来哉!剑阁峥嵘而崔嵬,一夫当关,万夫莫开。所守或匪亲,化为狼与豺。朝避猛虎,夕避长蛇;磨牙吮血,杀人如麻。锦城虽云乐,不如早还家。蜀道之难,难于上青天,侧身西望长咨嗟!',
)
const searchText = ref('')
const displayText = ref('')

onMounted(() => {
  handleSearch()
})

const handleSearch = () => {
  displayText.value = renderHighLights(text.value, searchText.value)
}

/**
 * @description: 此方法用于转义用户输入的特殊字符 如.*等等 避免影响正则表达式的生成导致匹配错误
 * @param {string} string
 * @return {string} 转义后的字符串
 */
const escapeRegExp = (string) => {
  // 转义正则表达式中的特殊字符
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& 表示整个匹配的子字符串
}

/**
 * @description: 关键词匹配高亮 此方法使用v-html片段渲染 需注意xss攻击
 * @param {string} text 原始文本
 * @param {string} keyword 关键词
 * @return {string} 替换后的html片段
 */
const renderHighLights = (text, keyword) => {
  if (keyword === '' || !text) {
    // 如果关键词为空或文本为空,直接返回原文本
    return text
  }

  // 转义关键词中的特殊字符 防止输入恶意字符
  const escapedKeyword = escapeRegExp(keyword)

  // 构建跨行匹配的正则表达式
  // 将关键词中的每个字符之间插入 \s* 以匹配任何数量的空白字符(包括换行符)
  const regex = new RegExp(escapedKeyword.split('').join('\\s*'), 'gi')
  // 使用正则表达式替换匹配的部分,并添加高亮效果
  return text.replace(
    regex,
    (match) => `<span style="background: yellow;">${match}</span>`,
  )
}

watch(searchText, () => {
  handleSearch()
})
</script>

<template>
  <div>
    <input type="text" v-model="searchText" placeholder="请输入...">
    <button @click="handleSearch">搜索并高亮</button>
    <div class="text" ref="content">
      <pre v-html="displayText"></pre>
    </div>
  </div>
</template>

<style scoped>
.text {
  width: 400px;
  height: 400px;
  padding: 0 10px;
  border: 1px solid #ddd;
  overflow: auto;
  pre {
    white-space: pre-wrap;
  }
  
}
</style>
文章来自个人专栏
前前又端端
1 文章 | 1 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0