一、场景需求
在项目中,经常会遇到文本内容超出容器的情况。为了提高用户体验,我希望在文字溢出时显示悬浮提示,未溢出时则不显示。
二、效果演示
三、实现原理
DOM宽度对比法:通过比较元素的scrollWidth(实际内容宽度)和clientWidth(可视区域宽度)判断是否溢出
动态绑定Tooltip:利用el-tooltip的disabled属性按需激活提示
响应式监听:结合Vue的$nextTick和watch实现动态数据更新后的自动检测
四、完整代码
<template>
<div class="box">
<div v-for="(item,i) in list" :key="i" class="items">
<!-- 添加 el-tooltip 并绑定判断逻辑 -->
<el-tooltip
:disabled="!shouldShowTooltip[i]"
:content="`${item.name}:${item.score}`"
placement="top">
<span
:ref="el => { nameElements[i] = el }"
class="name"
@mouseenter="checkOverflow(i)">
{{ item.name }}:{{ item.score }}
</span>
</el-tooltip>
</div>
</div>
</template>
<script>
export default {
data() {
return {
list: [
{
name: '我是测试名称1',
score: '90'
},
{
name: '我是测试我是测试我是测试我是测试',
score: '92'
},
{
name: '雪芽',
score: '99'
},
{
name: '果粒橙果粒橙果粒橙果粒橙果粒橙',
score: '100'
}
],
shouldShowTooltip: [], // 存储是否需要显示tooltip
nameElements: [] // 存储DOM引用
}
},
watch: {
list() {
this.$nextTick(() => {
this.list.forEach((_, i) => this.checkOverflow(i))
})
}
},
mounted() {
// 初始化时检查一次
this.$nextTick(() => {
this.list.forEach((_, i) => this.checkOverflow(i))
})
},
methods: {
// 通过对比实际宽度和可视宽度判断是否溢出
checkOverflow(index) {
const el = this.nameElements[index]
if (el) {
this.$set(this.shouldShowTooltip, index, el.scrollWidth > el.clientWidth)
}
}
}
}
</script>
<style lang="scss" scoped>
.box {
width: 300px;
font-size: 30px;
border: 1px solid red;
display: flex;
gap: 24px;
flex-direction: column;
.items {
flex: 1;
display: flex;
.name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
</style>
五、封装组件
如果项目此需求需求量大,可以将此逻辑封装成组件,便于不同页面使用~
1、组件:
<template>
<el-tooltip
:disabled="!showTooltip || disabled"
:content="content"
:placement="placement"
:popper-class="popperClass">
<div
ref="contentBox"
class="auto-tooltip-wrapper"
@mouseenter="handleCheckOverflow">
{{ content }}
</div>
</el-tooltip>
</template>
<script>
export default {
name: 'AutoTooltip',
props: {
disabled: Boolean, // 是否完全禁用功能(优先级最高)
content: String, // 显示内容(同时用于提示和内容区域)
// 提示框位置,参考ElementUI的placement配置
placement: {
type: String,
default: 'top'
},
popperClass: String, // 自定义Tooltip的类名
// 溢出容差(解决1像素级误差问题)
tolerance: {
type: Number,
default: 1
}
},
data() {
return {
showTooltip: false, // 控制提示显示状态
observer: null // ResizeObserver实例
}
},
mounted() {
this.initObserver()
this.checkOverflow()
},
beforeDestroy() {
if (this.observer) {
// 组件销毁时断开观察器
this.observer.disconnect()
}
},
methods: {
/**
* 初始化ResizeObserver
* 用于监听元素尺寸变化自动检测溢出状态
*/
initObserver() {
if (typeof ResizeObserver === 'undefined') return
try {
this.observer = new ResizeObserver(() => {
this.checkOverflow()
})
this.observer.observe(this.$refs.contentBox)
} catch (e) {
console.warn('ResizeObserver not supported')
}
},
/**
* 执行溢出检测的主方法
* 1. 获取DOM引用
* 2. 调用计算方法
* 3. 更新显示状态
*/
checkOverflow() {
const el = this.$refs.contentBox
if (!el) return
this.showTooltip = this.calculateOverflow(el)
},
/**
* 计算元素是否溢出
* @param {HTMLElement} el - 要检测的元素
* @returns {boolean} 是否发生溢出
*/
calculateOverflow(el) {
return el.scrollWidth > el.clientWidth + this.tolerance
},
/**
* 鼠标进入的回调检测(兼容模式)
* 当ResizeObserver不可用时,手动触发检测
*/
handleCheckOverflow() {
if (!this.observer) {
this.checkOverflow()
}
}
}
}
</script>
<style scoped>
.auto-tooltip-wrapper {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
box-sizing: border-box;
max-width: 100%;
}
</style>
2、页面内使用:
<template>
<div class="box">
<div v-for="(item,i) in list" :key="i" class="items">
<AutoTooltip :content="`${item.name}:${item.score}`"></AutoTooltip>
</div>
</div>
</template>
<script>
import AutoTooltip from './AutoTooltip.vue'
export default {
components: { AutoTooltip },
data() {
return {
list: [
{
name: '我是测试名称1',
score: '90'
},
{
name: '我是测试我是测试我是测试我是测试',
score: '92'
},
{
name: '雪芽',
score: '99'
},
{
name: '果粒橙果粒橙果粒橙果粒橙果粒橙',
score: '100'
}
],
shouldShowTooltip: [], // 存储是否需要显示tooltip
nameElements: [] // 存储DOM引用
}
},
watch: {
list() {
this.$nextTick(() => {
this.list.forEach((_, i) => this.checkOverflow(i))
})
}
},
mounted() {
// 初始化时检查一次
this.$nextTick(() => {
this.list.forEach((_, i) => this.checkOverflow(i))
})
},
methods: {
// 通过对比实际宽度和可视宽度判断是否溢出
checkOverflow(index) {
const el = this.nameElements[index]
if (el) {
this.$set(this.shouldShowTooltip, index, el.scrollWidth > el.clientWidth)
}
}
}
}
</script>
<style lang="scss" scoped>
.box {
width: 300px;
font-size: 30px;
border: 1px solid red;
display: flex;
gap: 24px;
flex-direction: column;
.items {
flex: 1;
display: flex;
.name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
</style>