vue渲染数组各子项实现文本超出宽度显示tooltip
需求背景
vue项目中,通过v-for渲染数组,子项中均存在一行描述文字。当描述文字超出固定宽度时,显示省略号并且鼠标悬浮时显示tooltip;当描述文字不超出固定宽度时则正常显示,且鼠标悬浮不显示tooltip
否决方案
否决的两个方案均基于文本宽度与容器宽度的计算,具体实现将在后续实际方案中再进行详细阐述。否决方案仅做思路阐述,不做过于具体的赘述
1.存储各子项宽度计算结果
- 具体实现:各子项描述文字容器绑定独有的ref,页面挂载完成后遍历各ref获取到对应元素并计算文本宽度与容器宽度关系,将计算结果存储在数组中。根据该结果通过v-if进行判断是否渲染tooltip
- 否决原因:需要计算并存储所有子项的宽度对比,在数组量较大的情况下会有一定的性能问题。且在部分情境下,还需要考虑页面resize事件后是否要重新计算的问题
2.宽度计算结果实时应用渲染
- 具体实现:各子项描述文字容器绑定独有的ref,各子项通过v-if绑定宽度计算函数,计算函数通过ref获取到对应元素并计算文本宽度与容器宽度关系,并返回计算结果。
- 否决原因:v-if绑定的计算函数,内部需要在页面渲染完成后才能获取到对应的元素进行计算,但是无论是使用
$nextTick
还是结合Promise微任务
都无法做到在渲染完成后计算结果并返回用于渲染更新
实现方案
宽度计算(核心)
// clientWidth为容器宽度,scrollWidth为文本完全展示时的实际宽度
isOverflowing(element) {
return element ? element.scrollWidth > element.clientWidth : false
},
tooltip控制
<!-- 固定渲染tooltip,但手动控制显隐 -->
<a-tooltip
:title="item.des"
placement="top"
:visible="visibleId == item.id">
</a-tooltip>
悬浮计算
<!-- 元素悬浮时才进行宽度计算 -->
<div
:ref="'textRef_' + item.id"
class="des"
@mouseenter="handleMouseEnter(item.id)"
@mouseleave="handleMouseLeave"
>
{{ item.des }}
</div>
计算结果存储
// 处理鼠标进入事件
handleMouseEnter(id) {
// 此处refs返回的是一个数组
// 若并非绑定在v-for数组内部的子元素上时,refs返回的则是单个元素,需注意区分
// 不一定需要refs才能获取到元素,通过事件本身传递的event对象也可获取到对应target
const element = this.$refs['textRef_' + id][0]
if (this.isOverflowing(element)) {
this.visibleId = id
}
},
// 处理鼠标离开事件
handleMouseLeave() {
this.visibleId = ''
},
完整代码
<template>
<div
v-for="(item, index) in list"
:key="index"
class="module_item"
>
<div class="module_header">
<div class="module_title">
<div class="title">{{ item.title }}</div>
<a-tooltip
:title="item.des"
placement="top"
:visible="visibleId == item.id"
>
<div
:ref="'textRef_' + item.id"
class="des"
@mouseenter="handleMouseEnter(item.id)"
@mouseleave="handleMouseLeave"
>
{{ item.des }}
</div>
</a-tooltip>
</div>
</div>
<div class="module_content">
<!-- 内容 -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
visibleId: '',
list: []
}
},
methods: {
isOverflowing(element) {
return element ? element.scrollWidth > element.clientWidth : false
},
// 处理鼠标进入事件
handleMouseEnter(id) {
const element = this.$refs['textRef_' + id][0]
if (this.isOverflowing(element)) {
this.visibleId = id
}
},
// 处理鼠标离开事件
handleMouseLeave() {
this.visibleId = ''
},
}
}
</script>
<style lang="less" scoped>
.module_item {
margin-top: 0.12rem;
&:first-child {
margin-top: 0;
}
.module_header {
display: flex;
justify-content: space-between;
align-items: center;
.module_title {
display: flex;
align-items: center;
gap: 0.08rem;
.title {
color: #000;
font-size: 0.14rem;
}
.des {
width: 1.83rem;
color: #999;
font-size: 0.1rem;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.modules {
margin-top: 0.08rem;
display: flex;
gap: 0.08rem;
}
}
</style>
总结
- 悬浮时计算,不需要一次性计算所有元素的计算结果,性能良好
- 仅存储单个宽度计算结果控制显隐,无需冗余数据
- 无需v-if进行判断渲染