一、场景需求
在项目中,经常会遇到文本内容超出容器的情况。为了提高用户体验,我希望在文字溢出时显示悬浮提示,未溢出时则不显示。
二、效果演示
三、实现原理
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: [], // 存储是否需要显示tooltipnameElements: [] // 存储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"><divref="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') returntry {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.contentBoxif (!el) returnthis.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: [], // 存储是否需要显示tooltipnameElements: [] // 存储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>