代码见文末
vue2实现
最近开发也用到了vue2,随即使用魔法优化改写了一下
vue3开发与使用文档:可自适应显示内容的 Tooltip 组件
组件介绍
该组件基于 Vue 3 和 Element Plus 构建,主要功能是通过 el-tooltip
实现内容的动态展示。若文本内容超出容器范围,则自动显示 Tooltip 提示框,同时支持自定义行数的裁剪(line-clamp
)。
开发背景
在前端开发中,长文本内容需要在有限空间中展示时,为了避免视觉杂乱,通常使用文字裁剪和 Tooltip 的组合方案。此组件适合展示动态内容或受限空间的文本场景。
使用指南
组件 Props
参数 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
toolTipOptions |
Object |
{placement: 'bottom', effect: 'dark', trigger: 'hover'} |
否 | Tooltip 配置项,支持所有 el-tooltip 的属性。 |
outStyle |
Object |
{ fontSize: '14px' } |
否 | 外层容器样式,支持传入自定义样式。 |
content |
String |
无 | 是 | 要显示的内容。 |
lineClamp |
Number |
1 |
是 | 显示行数,当超过行数时自动裁剪并显示 Tooltip。 |
maxWidth |
Number |
0 |
否 | Tooltip 的最大宽度(单位:px)。若为 0 ,则按当前文字容器宽度的 50% 设置。 |
示例代码
基础使用
<template>
<AdaptiveTooltip
:content="'这是一个很长的文本内容,当超出容器范围时会显示 Tooltip 提示框。'"
:lineClamp="2"
/>
</template>
<script setup>
import AdaptiveTooltip from './AdaptiveTooltip.vue';
</script>
自定义 Tooltip 配置
<template>
<AdaptiveTooltip
:toolTipOptions="{ placement: 'top', effect: 'light', trigger: 'click' }"
:content="'点击显示的 Tooltip 文本内容。'"
:lineClamp="1"
/>
</template>
限制 Tooltip 最大宽度
<template>
<AdaptiveTooltip
:content="'这是一个长文本,当文本超出容器范围时会显示 Tooltip,同时 Tooltip 宽度不超过 300px。'"
:lineClamp="1"
:maxWidth="300"
/>
</template>
自定义外部样式
<template>
<AdaptiveTooltip
:content="'自定义字体样式的文本内容。'"
:outStyle="{ color: '#f56c6c', fontWeight: 'bold' }"
:lineClamp="1"
/>
</template>
组件样式
文本裁剪样式
组件支持两种文本裁剪模式:- 单行文本裁剪(
text-auto-nowrap
):通过white-space: nowrap
实现单行裁剪。 - 多行文本裁剪(
text-auto-wrap
):通过 CSS 属性-webkit-line-clamp
实现多行裁剪。
- 单行文本裁剪(
Tooltip 样式
默认情况下,Tooltip 的宽度是文字容器宽度的 50%。开发者可通过maxWidth
修改。
开发注意事项
组件依赖
- Vue 3
- Element Plus
- TypeScript (可选)
动态宽度计算
Tooltip 宽度动态计算基于文字容器宽度,因此在onMounted
时绑定了 DOM 引用。行数校验
使用lineClamp
时,校验其值为大于 0 的整数。自适应溢出显示
根据scrollWidth
和clientWidth
判断文本是否超出容器范围。
代码
<template>
<div>
<el-tooltip
v-bind="props.toolTipOptions"
:popper-class="['tooltip-popper', { 'hide-tooltip': !toolTipShow }]"
>
<template #content>
<div class="tooltip-content" :style="{ maxWidth: contentMaxWidth }">
{{ props.content }}
</div>
</template>
<div
:class="{ 'text-auto-wrap': ifWrap, 'text-auto-nowrap': !ifWrap }"
ref="textAutoRef"
:style="{
'-webkit-line-clamp': props.lineClamp,
'line-clamp': props.lineClamp,
...props.outStyle
}"
>
{{ props.content }}
</div>
</el-tooltip>
</div>
</template>
<script setup lang="ts">
import { onMounted, defineProps, ref, computed } from 'vue';
interface ToolTipOptions {
content?: string;
placement?: 'top' | 'bottom' | 'left' | 'right';
effect?: 'dark' | 'light';
trigger?: 'hover' | 'click' | 'focus';
[key: string]: any;
}
const props = defineProps({
toolTipOptions: {
type: Object as () => ToolTipOptions,
default: () => ({
placement: 'bottom',
effect: 'dark',
trigger: 'hover',
}),
},
outStyle: {
type: Object,
default: () => ({
fontSize: '14px',
}),
},
content: {
type: String,
default: '',
required: true,
},
lineClamp: {
type: Number,
default: 1,
validator: (value: number) => value > 0,
},
maxWidth: {
type: Number,
default: 0,
},
});
const POPUP_WIDTH_RATIO = 0.5; // 弹出框宽度比例
const textAutoRef = ref<HTMLElement | null>(null);
const toolTipShow = ref(false);
const maxPopWidth = computed(() => {
if (textAutoRef.value) {
return textAutoRef.value.clientWidth * POPUP_WIDTH_RATIO;
}
return 0;
});
const contentMaxWidth = computed(() => {
if (props.maxWidth) return `${props.maxWidth}px`;
return `${maxPopWidth.value}px`;
});
const ifWrap = computed(() => {
return props.lineClamp > 1;
});
onMounted(() => {
if (!textAutoRef.value) return;
const isOverflow = !ifWrap.value
? textAutoRef.value.scrollWidth > textAutoRef.value.clientWidth
: textAutoRef.value.scrollHeight > textAutoRef.value.clientHeight;
toolTipShow.value = isOverflow;
});
</script>
<style lang="less" scoped>
.text-auto-nowrap {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
}
.text-auto-wrap {
width: 100%;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
}
:deep(.tooltip-popper) {
max-width: 50%;
}
:deep(.hide-tooltip) {
visibility: hidden !important;
}
</style>