文本溢出隐藏并使用tooltip
提示的需求,相信在平时的开发中会经常遇到。
常规做法一般是使用 elementui
的 el-tooltip
组件,在每次组件更新的时候去获取元素的宽度/高度判断是否被隐藏。
元素隐藏判断的文章,请戳:juejin.cn/post/740577...
受益于 element-plus
的虚拟触发 tooltip
的实现,决定探究在 vue2 上也以一种简单的方式实现 tooltip
提示。
探究 tooltip 源码
在 render
阶段,可以看出 tooltip 组件会提取插槽中的第一个子元素进行渲染
js
render(h) {
// ....
const firstElement = this.getFirstElement();
if (!firstElement) return null;
const data = firstElement.data = firstElement.data || {};
data.staticClass = this.addTooltipClass(data.staticClass);
return firstElement;
},
所以在 mounted
阶段, $el
会获取到的其实就是传入插槽的第一个元素。
并且在这个阶段,会给元素添加上mouseenter
、mouseleave
的事件监听,来控制 hover
状态下是否显示 tooltip
js
mounted() {
this.referenceElm = this.$el;
if (this.$el.nodeType === 1) {
this.$el.setAttribute('aria-describedby', this.tooltipId);
this.$el.setAttribute('tabindex', this.tabindex);
on(this.referenceElm, 'mouseenter', this.show);
on(this.referenceElm, 'mouseleave', this.hide);
// ...
}
函数式调用 tooltip
在了解了 el-tooltip
的运行原理之后,我们能够封装一个模板,并且支持函数式调用。
通过
getEl
获取一个 DOM 元素,以便在唤起tooltip
时元素的所处位置。
html
<template>
<el-tooltip ref="triggerRef" :manual="true">
<template #content>
{{ internalContent }}
</template>
</el-tooltip>
</template>
<script>
// useOverflowHidden 的逻辑自行定义
import { useOverflowHidden } from './use-overflow-hidden'
export default {
name: 'TooltipFunction',
props: {
getEl: {
type: Function,
default: () => null
},
getContent: {
type: Function,
default: () => ''
}
},
data() {
return {
internalContent: '',
isHover: false,
}
},
mounted() {
const el = this.getEl()
if (!el) return
this.$refs.triggerRef.referenceElm = el;
el.addEventListener('mousemove', this.onMouseEnter, false)
el.addEventListener('mouseleave', this.onMouseLeave, false)
},
methods: {
onMouseEnter() {
if (!this.isHover && useOverflowHidden(this.getEl())) {
this.internalContent = this.getContent()
this.isHover = true
this.$refs.triggerRef.showPopper = true
}
},
onMouseLeave() {
if (this.isHover) {
this.isHover = false;
this.$refs.triggerRef.showPopper = false
}
},
onDestroy() {
const el = this.getEl()
if (!el) return
el.removeEventListener('mousemove', this.onMouseEnter)
el.removeEventListener('mouseleave', this.onMouseLeave)
}
},
}
</script>
模版完成过后,我们还需要再写一个函数用来调用 TooltipFunction
。
js
import Vue from 'vue'
import TooltipFunction from './tooltipFunction.vue'
function useTooltip(el) {
const Ctor = Vue.extend(TooltipWrapper)
const instance = new Ctor({
propsData: {
getContent,
getEl: () => el,
},
})
instance.$mount()
return instance
}
在代码中,我们只需在 mounted
中对需要 tooltip
的元素进行一次注册即可。
html
<template>
<div ref="dataRef">foo</div>
</template>
<script>
import useTooltip from './use-tooltip.js'
export default {
mounted() {
const el = this.$refs.dataRef
if (el) {
// 获取 content 的函数逻辑自行定义
this.tooltipIns = useTooltip(el, () => 'foo')
}
},
beforeDestory() {
this.tooltipIns?.onDestroy()
this.tooltipIns?.$destroy()
}
}
</script>
自定义指令 v-tooltip
上述虽然实现了函数式调用 tooltip 的方式,但是还需要对每个元素进行 ref
声明获取 DOM,以及组件销毁时 tooltip
的生命周期管理。vue 的自定义指令恰好能轻松解决这两件事情。
js
function setupTooltipDirection() {
const tooltipSymbol = Symbol('tooltip')
Vue.directive('tooltip', {
bind(el: HTMLElement) {
// 这里我们使用 DOM 元素上的 tooltip-content 作为 通信
const instance = createTooltipFactory(el, () => el.getAttribute('tooltip-content') || '')
Reflect.set(el, tooltipSymbol, instance)
},
unbind(el) {
const ins = Reflect.get(el, tooltipSymbol)
if (!ins) return
ins.onDestroy()
ins.$destroy()
Reflect.set(el, tooltipSymbol, null)
},
})
}
在业务中,即可通过简单的指令实现 tooltip 的提示需求:
html
<template>
<div v-tooltip tooltip-content="hello">hello</div>
</template>