自定义Vue
指令,使用 element-plus
实现文本溢出后鼠标悬浮显示tooltip的功能
需求
文本超出盒子宽度后显示省略号,鼠标悬浮到该盒子上时,使用ElTooptip
组件显示文本的全部内容,没有超出的文本鼠标悬浮时不显示ElTooptip
。
效果图
实现思路
使用
CSS
设置文本超出后显示省略号,代码如下:css.box { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
自定义
Vue
指令,指令的工作流程如下:- 给绑定指令的
DOM
元素添加mouseenter
事件。 - 事件触发后实时计算文本占用的原始宽度和显示的宽度进行比较。
- 如果原始占用的宽度大于显示的宽度,则表示内容被隐藏掉了。
- 使用
Vue
的h
或createVNode
函数动态创建ElTooptip
,然后使用render
函数进行渲染就可以了。
- 给绑定指令的
代码实现
计算文本占用宽度,判断是否需要创建ElTootip
组件。
typescript
const range = document.createRange()
range.setStart(el, 0)
range.setEnd(el, el.childNodes.length)
let rangeWidth = range.getBoundingClientRect().width
const offsetWidth = rangeWidth - Math.floor(rangeWidth)
const { width: cellChildWidth } = el.getBoundingClientRect()
if (offsetWidth < 0.001) {
rangeWidth = Math.floor(rangeWidth)
}
if (rangeWidth > cellChildWidth) {
// 创建Tooltip组件
createTooltip(el.innerText, el, binding.value || {})
}
创建ElTooltip
组件。
typescript
let removePopper: RemovePopperFn | null = null
const createTooltip = (content: string, trigger: HTMLElement, props: TextOverflowProp) => {
if (removePopper?.trigger === trigger) {
return
}
removePopper?.()
const vm = createVNode(ElTooltip, {
content: content,
virtualTriggering: true,
virtualRef: trigger,
hideAfter: 0,
...props,
onHide: () => {
removePopper?.()
}
})
const container = document.createElement('div')
render(vm, container)
vm.component!.exposed!.onOpen()
const scrollContainer = document.querySelector('.el-scrollbar__wrap')
removePopper = () => {
render(null, container)
scrollContainer?.removeEventListener('scroll', removePopper!)
removePopper = null
}
removePopper.trigger = trigger
scrollContainer?.addEventListener('scroll', removePopper)
}
text-overflow/index.ts
完整代码如下:
typescript
import { ElTooltip } from 'element-plus'
import { createVNode, render } from 'vue'
import type { CSSProperties, Directive, DirectiveBinding, VNode } from 'vue'
type RemovePopperFn = (() => void) & {
trigger?: HTMLElement
}
export interface TextOverflowProp {
placement?:
| 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
| 'right'
| 'right-start'
| 'right-end'
content?: string
offset?: number
}
let removePopper: RemovePopperFn | null = null
const createTooltip = (content: string, trigger: HTMLElement, props: TextOverflowProp) => {
if (removePopper?.trigger === trigger) {
return
}
removePopper?.()
const vm = createVNode(ElTooltip, {
content: content,
virtualTriggering: true,
virtualRef: trigger,
hideAfter: 0,
...props,
onHide: () => {
removePopper?.()
}
})
const container = document.createElement('div')
render(vm, container)
vm.component!.exposed!.onOpen()
const scrollContainer = document.querySelector('.el-scrollbar__wrap')
removePopper = () => {
render(null, container)
scrollContainer?.removeEventListener('scroll', removePopper!)
removePopper = null
}
removePopper.trigger = trigger
scrollContainer?.addEventListener('scroll', removePopper)
}
/**
* 文本超出显示范围后鼠标放到文本上显示tooltip
*/
const textOverflowDirective = {
mounted: (el: HTMLElement, binding: DirectiveBinding<TextOverflowProp>) => {
el.onmouseenter = () => {
if (el.childNodes.length === 0) {
return
}
const range = document.createRange()
range.setStart(el, 0)
range.setEnd(el, el.childNodes.length)
let rangeWidth = range.getBoundingClientRect().width
const offsetWidth = rangeWidth - Math.floor(rangeWidth)
const { width: cellChildWidth } = el.getBoundingClientRect()
if (offsetWidth < 0.001) {
rangeWidth = Math.floor(rangeWidth)
}
if (rangeWidth > cellChildWidth) {
createTooltip(el.innerText, el, binding.value || {})
}
}
}
} as Directive<HTMLElement, TextOverflowProp>
export const vTextOverflow = textOverflowDirective
export default vTextOverflow
使用示例
vue
<script setup lang="ts">
import vTextOverflow from '@/components/directives/text-overflow'
</script>
<template>
<div class="box">
<ul>
<li v-text-overflow>这是第一条这是第一条这是第一条这是第一条</li>
<li v-text-overflow>这是第二条</li>
<li v-text-overflow>这是第三条</li>
<li v-text-overflow>这是第四条这是第四条这是第四条这是第四条</li>
</ul>
</div>
</template>
<style scoped lang="scss">
.box {
width: 200px;
height: 300px;
border: 2px solid #eee;
margin: 40px 100px;
padding: 10px;
}
ul {
margin: 0;
padding: 0;
list-style: none;
line-height: 36px;
li {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>