利用el-tooltip实现通过指令实现文案超出显示提示

101 阅读1分钟
<script setup lang="ts">
import {
  ref,
  h,
  getCurrentInstance,
  render,
  type DirectiveBinding,
} from "vue";
import { ElTooltip } from "element-plus";

const textRef = ref();
const text = ref("test tooltip overflow");

// 辅助函数:验证属性名是否合法(如非数字开头)
function isValidAttributeName(name: string) {
  return /^[a-zA-Z_][\w-]*$/.test(name);
}

const vTooltip = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    el.onmouseover = function (e) {
      if (
        el &&
        el.firstElementChild &&
        el.clientWidth < el.firstElementChild.offsetWidth
      ) {

        // 1. 提取原元素的合法属性(过滤非法名称)
        const attributes = Array.from(el.attributes).reduce(
          (attrs, { name, value }) => {
            if (isValidAttributeName(name)) {
              // 过滤合法属性名
              attrs[name] = value;
            }
            return attrs;
          },
          {}
        );

        // 1. 创建目标元素的虚拟节点(VNode),而非克隆原生 DOM
        const targetVNode = h(
          el.tagName.toLowerCase(), // 标签名
          {
            // 继承原元素的属性、事件、类名等
            ...attributes,
            class: el.className,
            style: el.style.cssText,
          },
          // 继承子节点(文本或子元素)
          Array.from(el.childNodes).map((node) => node.textContent)
        );
        // 创建 ElTooltip 的虚拟节点,并将原目标元素作为其子节点
        const tooltipVNode = h(
          ElTooltip,
          {
            content: binding.value.content,
            placement: "top",
          },
          // 默认插槽:直接传递目标元素的 VNode
          { default: () => targetVNode }
        );

        // 创建临时容器 DOM,替换原父元素中的目标元素?
        const tempContainer = document.createDocumentFragment();

        // 使用当前组件上下文渲染 Tooltip(关键步骤!)
        render(tooltipVNode, tempContainer);
        
        if (el.parentNode) {
          el.parentNode.replaceChild(tempContainer, el);
        }
      }
    };
  },
};
</script>

<template>
  <div class="text" v-tooltip="{ content: text }">
    <span ref="textRef" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave">{{
      text
    }}</span>
  </div>
 </template>