基于ElementUI实现v-tooltip指令

文本溢出隐藏并使用tooltip 提示的需求,相信在平时的开发中会经常遇到。

常规做法一般是使用 elementuiel-tooltip 组件,在每次组件更新的时候去获取元素的宽度/高度判断是否被隐藏。

元素隐藏判断的文章,请戳:juejin.cn/post/740577...

受益于 element-plus的虚拟触发 tooltip 的实现,决定探究在 vue2 上也以一种简单的方式实现 tooltip 提示。

探究 tooltip 源码

源码地址:github.com/ElemeFE/ele...

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 会获取到的其实就是传入插槽的第一个元素。

并且在这个阶段,会给元素添加上mouseentermouseleave的事件监听,来控制 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>
相关推荐
拄杖盲学轻声码6 分钟前
【html网页制作】国庆节日主题网页制作含js轮播(5页面附效果源码)
前端·javascript·html
customer0813 分钟前
【开源免费】基于SpringBoot+Vue.JS服装销售平台(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
you来有去18 分钟前
npm install报错npm ERR! Found: vite@4.5.0
前端·npm·node.js
等什么君!39 分钟前
初识Vue3(详细版)
前端·vue
程序员奇奥1 小时前
Vue中对数组变化监听
前端·javascript·vue.js
胡西风_foxww1 小时前
用css画一个loading
前端·css·loading·加载中
i80131 小时前
弹性盒模型关键几个点:
前端·javascript·css
时清云1 小时前
【算法】搜索二维矩阵
前端·算法·面试
挣钱花3881 小时前
mapboxGL 离线部署或者说去除token最简单得方法
开发语言·前端·javascript
喵喵酱仔__1 小时前
css 数字比汉字要靠上
前端·css