记录一下自己独立写的高亮文字工具

444 阅读2分钟

背景:

一段文章需要高亮部分内容,过去的实现方式非常复杂,而且反应很慢,可复用性也差,所以将其中高亮流程部分整体重构了。其中比较重要的就是这个高亮工具,因为上层vue组件是直接使用的工具,不做任何处理的。所以工具的目的就是输入文字与数组,输出html,能直接放进组件中的v-html,不需要任何处理。

目的:

  • 功能目的: 输入:
  1. 需要高亮的一段文字,比如'天青色等烟雨,而我在等你。月色被打捞起,晕开了结局'
  2. 需要高亮数组,例如[[1,3],[2,4],[7,9]],此时意思为第一个字到第四个子高亮,第二个字到第四个子高亮...
  3. 高亮部分样式 输出: 带有高亮样式标签包裹的html文本 例如:天<span style='background: '#F6CC15''>青色等烟</span>雨,<span style='background: '#F6CC15''>而我在</span>等你。月色被打捞起,晕开了结局

以下为工具具体实现:

import { kebabCase } from 'lodash-es'
/**
 * 文字高亮组件
 *
 * @param {string} content 需要高亮的文字内容
 * @param {Array} arr 高亮部分数组。[[start,end],[start,end]...]
 * @param {object} style 高亮部分样式
 * @return {string} 返回高亮html
 */

const defaultStyle = {
  background: '#F6CC15'
}
export function useHighlightText(content, arr = [], style) {
  if (!content && content !== 0) {
    return ''
  }
  if (!Array.isArray(arr) || arr?.length === 0) {
    return content
  }
  style = Object.assign({}, defaultStyle, style)
  const contentStyle = (() => {
    let s = []
    for (let i in style) {
      let attrName = kebabCase(i) // 样式对象中的大写字母驼峰处理,例如:borderRadius会转换成css能识别的border-radius
      s.push(attrName + ':' + style[i])
    }
    s = s.join(';')
    return s
  })()
  // arr中包含[0,0]的,表示要全部高亮  eg:[[0,0]] 或者 [[0,0],[3,9]]
  let findItem = arr.find(item => item.join() === '0,0')
  if (findItem) {
    return '<span style="' + contentStyle + ';">' + content + '</span>'
  }
  const contentArr = (() => {
    // 去除重复值及重构,以避免标签混乱,例arr = [[1,5],[2,6],[7,9]]
    // 数组展开 => [[1,2,3,4],[2,3,4,5],[7,8]]
    const openArr = arr.map(item => {
      const temporary = []
      for (let i = item[0]; i < item[1]; i++) {
        temporary.push(i)
      }
      return temporary
    })
    // 数组扁平化->去重->排序 => [1,2,3,4,5,7,8]
    const openRemovalDuplicateSortArr = Array.from(new Set(openArr.flat())).sort((a, b) => a - b)
    // 数组重构成[[start,start...],[end,end...]] => [[1,7],[5,8]]
    const returnArr = [[], []]
    for (let j = 0; j < openRemovalDuplicateSortArr.length; j++) {
      if (j === 0) {
        returnArr[0].push(openRemovalDuplicateSortArr[j])
      } else if (j === openRemovalDuplicateSortArr.length - 1) {
        returnArr[1].push(openRemovalDuplicateSortArr[j])
      } else if (openRemovalDuplicateSortArr[j + 1] - openRemovalDuplicateSortArr[j] !== 1) {
        returnArr[1].push(openRemovalDuplicateSortArr[j])
      } else if (openRemovalDuplicateSortArr[j] - openRemovalDuplicateSortArr[j - 1] !== 1) {
        returnArr[0].push(openRemovalDuplicateSortArr[j])
      }
    }
    return returnArr
  })()
  let temText = ''
  for (let i = 0; i < content.length; i++) {
    if (contentArr[0].includes(i)) {
      temText = temText + '<span style="' + contentStyle + ';">' + content[i]
    } else if (contentArr[1].includes(i)) {
      temText = temText + content[i] + '</span>'
    } else {
      temText = temText + content[i]
    }
  }
  return temText
}

// 此方法可以用innerText代替,性能更好
export function removeHighlightText(content, style = defaultStyle) {
  const contentStyle = (() => {
    let s = []
    for (let i in style) {
      s.push(i + ':' + style[i])
    }
    s = s.join(';')
    return s
  })()
  return content.replace('<span style="' + contentStyle + ';">', '').replace('</span>', '')
}