<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>