嗨,大家好,我是爱笑的阿呆,一个前端圈的小菜鸟。今天聊一下在工作中如何基于element-plus封装一个超好用的文本超出自动显示tooltip的组件与指令。
效果展示
难点
文本超出如何显示省略号
使用 -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>