实现类似于 Element UI 表格的溢出文本提示功能

在 Element UI 的表格组件中,当表格列的内容过长时,设置 show-overflow-tooltip 会自动显示一个 tooltip 来展示完整的内容。这个功能在实际项目中也是非常常见的,那么我们该如何实现这个功能呢?

1 Demo

先来看一下效果:demo

2 实现代码

直接贴上完整的代码,通过一个自定义指定 v-overflow-tooltip 来实现:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
const setTooltip = (el, binding) => {
  // 设置内容
  el.innerText = binding.value

  const elComputed = document.defaultView.getComputedStyle(el, '')
  const padding = parseInt(elComputed.paddingLeft.replace('px', '')) + parseInt(elComputed.paddingRight.replace('px', ''))
  const range = document.createRange()
  range.setStart(el, 0)
  range.setEnd(el, el.childNodes.length)
  const rangeWidth = range.getBoundingClientRect().width
  const isEllipsis = rangeWidth + padding > el.offsetWidth || el.scrollWidth > el.offsetWidth

  // 鼠标移入时,将浮层元素插入到 body 中
  el.onmouseenter = function(e) {
    if (!isEllipsis) { return }
    // 创建浮层元素并设置样式
    const vcTooltipDom = document.createElement('div')
    Object.assign(vcTooltipDom.style, {
      position: 'absolute',
      background: '#303133',
      color: '#fff',
      fontSize: '12px',
      zIndex: '6000',
      padding: '10px',
      borderRadius: '4px',
      lineHeight: 1.2,
      minHeight: '10px',
      wordWrap: 'break-word',
    })
    // 设置 id 方便寻找
    vcTooltipDom.setAttribute('id', 'vc-tooltip')
    // 将浮层插入到 body 中
    document.body.appendChild(vcTooltipDom)
    // 浮层中的文字 通过属性值传递动态的显示文案
    document.getElementById('vc-tooltip').innerHTML = binding.value
  }
  // 鼠标移动时,动态修改浮层的位置属性
  el.onmousemove = function(e) {
    if (!isEllipsis) { return }
    const vcTooltipDom = document.getElementById('vc-tooltip')
    const padding = 5
    let offsetX = e.clientX + 15
    let offsetY = e.clientY + 15
    // 判断是否超出视窗边界(横向)
    if (offsetX + vcTooltipDom.offsetWidth > document.documentElement.clientWidth) {
      offsetX = document.documentElement.clientWidth - vcTooltipDom.offsetWidth - padding
    }
    if (offsetX <= 0) {
      offsetX = padding
      vcTooltipDom.style.width = document.documentElement.clientWidth - padding * 2 + 'px'
    }
    // 判断是否超出视窗边界(纵向)
    if (offsetY + vcTooltipDom.offsetHeight > document.documentElement.clientHeight) {
      offsetY = document.documentElement.clientHeight - vcTooltipDom.offsetHeight - padding
    }
    if (offsetY <= 0) {
      offsetY = padding
      vcTooltipDom.style.height = document.documentElement.clientHeight - padding * 2 + 'px'
    }
    vcTooltipDom.style.left = offsetX + 'px'
    vcTooltipDom.style.top = offsetY + 'px'
    // 注:当浮层元素和窗口大小差不多时,浮层会覆盖原本的内容,导致浮层闪一下就不见了
  }
  // 鼠标移出时将浮层元素销毁
  el.onmouseleave = function() {
    if (!isEllipsis) { return }
    // 找到浮层元素并移出
    const vcTooltipDom = document.getElementById('vc-tooltip')
    vcTooltipDom && document.body.removeChild(vcTooltipDom)
  }
}

const plugin = {
  install(Vue) {
    Vue.directive('overflow-tooltip', {
      inserted: (el, binding) => {
        // 设置元素样式
        Object.assign(el.style, {
          overflow: 'hidden',
          textOverflow: 'ellipsis',
          whiteSpace: 'nowrap',
        })
        // 监控元素可见性变化
        const observer = new IntersectionObserver((entries) => {
          if (entries[0].isIntersecting) {
            setTooltip(el, binding)
          }
        })
        observer.observe(el)
        // 监控元素宽度变化
        const resizeObserver = new ResizeObserver(() => {
          setTooltip(el, binding)
        })
        resizeObserver.observe(el)
        // 设置浮层内容
        setTooltip(el, binding)
      },
      update: (el, binding) => {
        // 更新浮层内容
        setTooltip(el, binding)
      },
      unbind: (el) => {
        el.onmouseenter = null
        el.onmousemove = null
        el.onmouseleave = null
      },
    })
  }
}

let GlobalVue = null
if (typeof window !== 'undefined') {
  GlobalVue = window.Vue
} else if (typeof global !== 'undefined') {
  GlobalVue = global.Vue
}

if (GlobalVue) {
  GlobalVue.use(plugin)
}

export default plugin

使用很简单,导入并注册之后,就可以在需要的地方使用 v-overflow-tooltip 指令了:

1
2
import overflowTooltip from '@/directives/overflow-tooltip'
Vue.use(overflowTooltip)

比如说:

1
<span v-overflow-tooltip="content" style="display: inline-block; width: 100px;" />

3 实现原理

  1. 通过 getComputedStyle 获取元素的 padding 值,然后通过 createRange 获取元素的宽度。
  2. 如果元素的内容宽度大于元素的宽度,那么就显示 tooltip。
  3. 鼠标移入时,将浮层元素插入到 body 中,鼠标移动时,动态修改浮层的位置属性,鼠标移出时将浮层元素销毁。(浮层需要做边界检测)

其中最关键的一段代码是:

1
2
3
4
const range = document.createRange()
range.setStart(el, 0)
range.setEnd(el, el.childNodes.length)
const rangeWidth = range.getBoundingClientRect().width

这段代码是通过 createRange 设置元素的范围,然后通过 getBoundingClientRect 获取元素的宽度。


相关内容

Buy me a coffee~
Lruihao 支付宝支付宝
Lruihao 微信微信
0%