vue指令式tooltip,让你的使用更便捷

243 阅读2分钟

嗨,大家好,我是爱笑的阿呆,一个前端圈的小菜鸟。今天聊一下在工作中如何基于element-plus封装一个超好用的文本超出自动显示tooltip的组件与指令

效果展示

ezgif-4-32314a0224.gif

难点

文本超出如何显示省略号

使用 -webkit-line-clamp 属性

// $line 代表最多显示几行
@mixin line-cut($line) {
    overflow: hidden;
    text-overflow: ellipsis;
    word-break: break-all;
    display: -webkit-box;
    -webkit-line-clamp: $line;
    -webkit-box-orient: vertical;
}

如何判断文本超出显示区域

// scrollHeight 是一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容。
// clientHeight 是一个元素内容高度的度量,只包括视图可见部分;
function isTextOverflow(element: HTMLElement) {
  return element.scrollHeight > element.clientHeight;
}

组件实现

实现代码

<template>
    <el-tooltip :content="content" :disabled="!isOverflow" v-bind="$attrs">
        <template #default>
            <div ref="defaultDom">
                <slot name="default"></slot>
            </div>
        </template>
    </el-tooltip>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';

export default defineComponent({
    props: {
        content: {
            type: String,
            default: '',
        },
    },
    setup() {
        const defaultDom = ref();
        const isOverflow = ref(false);
        onMounted(() => {
            if (defaultDom.value?.children) {
                isOverflow.value = isTextOverflowing(defaultDom.value?.children[0]);
            }
        })
        function isTextOverflowing(element: HTMLElement) {
            return element.scrollHeight > element.clientHeight;
        }
        return {
            defaultDom,
            isOverflow,
        };
    }
});
</script>

使用方式

<template>
    <TooltipHelp :content="componentContent1" placement="top">
      <div class="line-cut-1 w-400">{{ componentContent1 }}</div>
    </TooltipHelp>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import TooltipHelp from './components/tooltip-help/index.vue';
const componentContent1 = ref('我是一行超出,我有好多文字,我有好多文字,我有好多文字,我有好多文字,我有好多文字,我有好多文字,我有好多文字');
</script>

vue指令实现

实现思路

  • 利用 Tooltip 组件的虚拟触发功能,将指令绑定的元素作为 Tooltip 的触发元素
  • 将指令绑定的值变为 响应式数据,然后在 updated 钩子函数中更新 Tooltip 组件的值
  • unmounted 钩子函数中进行相关的销毁工作

实现代码

import { ElTooltip } from "element-plus";
import { createApp, defineComponent, h, DirectiveBinding, toRefs, reactive, App, ComponentPublicInstance } from "vue";

export interface TooltipElement extends HTMLElement {
  _tooltipInstance?: ComponentPublicInstance & {
    content?: string;
    effect?: string;
    placement?: string;
    rawContent?: boolean;
    disabled?: boolean;
  };
  _tooltipApp?: App<Element>;
}

// 判断文本是否溢出
function isTextOverflow(element: HTMLElement) {
  return element.scrollHeight > element.clientHeight;
}

export const vTooltip = {
  mounted(el: TooltipElement, binding: DirectiveBinding) {
    const options = reactive({
      content: binding.value.content || "",
      effect: binding.value.effect || "dark",
      placement: binding.value.placement || "top",
      rawContent: binding.value.rawContent || false,
      disabled: !isTextOverflow(el)
    })
    // 创建 tooltip 组件的实例
    const TooltipComponent = defineComponent({
      setup() {
        return { ...toRefs(options) }
      },
      render() {
        return h(
          ElTooltip,
          {
            content: this.content,
            effect: this.effect,
            placement: this.placement,
            rawContent: this.rawContent,
            disabled: this.disabled,
            virtualRef: el,
            virtualTriggering: true, // 使用虚拟触发
          },
        );
      },
    });
    // 创建并挂载 Vue 应用
    const app = createApp(TooltipComponent);
    const tooltipInstance = app.mount(document.createElement('div'));
    // 保存实例以便后续更新
    el._tooltipInstance = tooltipInstance; 
    el._tooltipApp = app; 
  },
  updated(el: TooltipElement, binding: DirectiveBinding) {
    el._tooltipInstance!.content = binding.value.content || binding.oldValue.content;
    el._tooltipInstance!.placement = binding.value.placement || binding.oldValue.placement;
    el._tooltipInstance!.effect = binding.value.effect || binding.oldValue.effect;
    el._tooltipInstance!.rawContent = binding.value.rawContent || binding.oldValue.rawContent;
    el._tooltipInstance!.disabled = !isTextOverflow(el);
  },
  unmounted(el: TooltipElement) {
    el._tooltipApp?.unmount();
    el._tooltipInstance = undefined;
    el._tooltipApp = undefined;
  },
};

使用方式

<template>
    <div class="line-cut-1 w-400" 
        v-tooltip="{ 
            content: directiveContent1, 
            placement: directivePlacement
        }"
    >
        {{ directiveContent1 }}
    </div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const directiveContent1 = ref('我是一行超出,我有好多文字,我有好多文字,我有好多文字,我有好多文字,我有好多文字,我有好多文字,我有好多文字');
const directivePlacement = ref('top');
</script>