从零开始Vue3+Element Plus后台管理系统(27) 按需显示的 overflow-tooltip

el-table 的 show-overflow-tooltip 虽然可以帮我们在单元格内容过长时,隐藏多余内容,并自动加上 tooltip,但是只要加上了show-overflow-tooltip,未超长的内容也会有 tooltip,有时候觉得比较烦人,如果改成按实际情况来显示 tooltip ,会不会体验好一些呢?

如果只考虑 td 内容显示一行,那么只需判断完整文本的宽度是不是超过 td 的文本宽度就可以了。

实际应用中,我们会碰到 td 内容显示 2 行或者更多的情况,所以在开发组件时,可以考虑更多的应用场景,不仅适用表格,还可以应用到其他元素中。

这实际又是一个判断多行文本溢出的问题,和上一篇文章《从零开始Vue3+Element Plus后台管理系统(26) 多行文字展开折叠》的思路不谋而合,而且会更简单。

基础实现

我们已知多行文本溢出省略的 CSS。重点就在line-clamp 和 height,只要确定了它俩基本就实现了一半

css 复制代码
  position: relative;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  word-break: break-all;
  white-space: pre-wrap;
  line-clamp:2;
  height:46px;
  • row,作为 props 参数传递
  • height,根据 row 和当前html元素的行高可以计算出来
html 复制代码
<template>
  <div ref="refTd">
    <el-tooltip placement="top" :disabled="!isExceed">
      <template #content>
        <p class="max-w-100">{{ text }}</p>
      </template>
      <div class="text-wrap" :style="tdStyle">
        <span class="text-content" ref="refContent">{{ text }}</span>
      </div>
    </el-tooltip>
  </div>
</template>
js 复制代码
// ...
const tdStyle = reactive({
  lineClamp: 1,
  height: 'auto'
})

watch(
  () => props.text,
  async (text) => {
    if (text) {
      await nextTick()
      const lineHeight = Number(window.getComputedStyle(refTd.value).lineHeight.slice(0, -2))

      const fullHeight = refContent.value?.offsetHeight
      isExceed.value = fullHeight > props.row * lineHeight

      tdStyle.lineClamp = props.row
      // 如果没有超过一行,则不需要设置高度
      if (isExceed.value) tdStyle.height = props.row * lineHeight + 'px'
    }
  },
  { immediate: true }
)

通过判断是否超出 isExceed ,设置 el-tooltip 的 disabled属性 <el-tooltip placement="top" :disabled="!isExceed">,目前为止,已经基本实现了超出显示 tooltip,不超长则正常显示的效果。

不过,当我去拖动表格修改列宽时,有了新问题,虽然宽度增加了,文本可以全部显示出来,但是 tooltip 的效果依然存在。依然是自适应尺寸的问题,那么继续使用Resize Observer来优化组件代码。

优化掉坑

没想到,这个优化让我掉在坑里了。。。

实时监听内容区域尺寸,当尺寸变化时,需要重新获取完整文本高度。默认情况下,子元素高度受父元素影响,获取的高度和父元素是一样的,也就判断不出来是否超出高度。

为了让它脱离文档流,设置 position:absolute,这样可以获取到完整高度。但是因为脱离父元素的文档流,溢出后的省略号 ... 也消失了。

我尝试在尺寸变化时,实时修改子元素 refContent 的position为 absolute,之后再设置为relative,可惜没有产生效果。

最终的解决方案是在父元素下直接插入文本,refContent 也保留,这样既可以通过 refContent 获取高度,又可以正常显示。至少在页面上看起来是正常的。

还有一种解决方式,是临时创建一个html元素,设置宽度为监听到的元素宽度,然后计算该元素的高度,之后再移除。与现在使用的方案相比,感觉都不是很好的方案,因为该组件的主要应用场景在表格中,重复操作 DOM 的次数比较多。

虽然实现了想要的效果,但是感觉并不完美,希望有更好的方案。

组件代码

以下是最终的代码,抛砖引玉

js 复制代码
<template>
  <div ref="refTd">
    <el-tooltip placement="top" :disabled="!isExceed">
      <template #content>
        <p class="max-w-100">{{ text }}</p>
      </template>
      <div class="text-wrap" :style="tdStyle">
        {{ text }}
        <div class="text-content" ref="refContent">{{ text }}</div>
      </div>
    </el-tooltip>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, watch, nextTick, onMounted, onUnmounted } from 'vue'

// 定义属性类型
const props = defineProps({
  text: { type: String, default: '' },
  row: { type: Number, default: 1 }
})

const refTd = ref()
const refContent = ref()

const tdStyle = reactive({
  lineClamp: 0,
  height: 'auto'
})

let resizeObserver: ResizeObserver | null = null
let fullHeight = 0
let lineHeight = 0

watch(
  () => props.text,
  async (text) => {
    if (text) {
      await nextTick()
      lineHeight = Number(window.getComputedStyle(refContent.value).lineHeight.slice(0, -2))
    }
  },
  { immediate: true }
)

onMounted(async () => {
  resizeObserver = new ResizeObserver(async (entries) => {
    for (let entry of entries) {
      fullHeight = entry.contentRect.height
      handleExceed()
    }
  })

  resizeObserver.observe(refContent.value)
})

const isExceed = ref(false)
const handleExceed = async () => {
  isExceed.value = fullHeight > props.row * lineHeight

  tdStyle.lineClamp = props.row
  // 如果没有超过一行,则不需要设置高度
  if (isExceed.value) tdStyle.height = props.row * lineHeight + 'px'
  else tdStyle.height = fullHeight + 'px'
}

const unobserveElementSize = () => {
  if (resizeObserver) {
    resizeObserver.disconnect()
    resizeObserver = null
  }
}

onUnmounted(() => {
  unobserveElementSize()
})
</script>

<style lang="scss" scoped>
.text-wrap {
  position: relative;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  word-break: break-all;
  white-space: pre-wrap;

  .text-content {
    position: absolute;
    visibility: hidden;
  }
}
</style>

项目地址

本项目GIT地址:github.com/lucidity99/...

如果有帮助,给个star ✨ 点个赞

相关推荐
林太白2 分钟前
Nuxt3 功能篇
前端·javascript·后端
YuJie3 分钟前
webSocket Manager
前端·javascript
Mapmost18 分钟前
Mapmost SDK for UE5 内核升级,三维场景渲染效果飙升!
前端
Mapmost21 分钟前
重磅升级丨Mapmost全面兼容3DTiles 1.1,3DGS量测精度跃升至亚米级!
前端·vue.js·three.js
wycode27 分钟前
Promise(一)极简版demo
前端·javascript
浮幻云月28 分钟前
一个自开自用的Ai提效VsCode插件
前端·javascript
DevSecOps选型指南29 分钟前
SBOM风险预警 | NPM前端框架 javaxscript 遭受投毒窃取浏览器cookie
前端·人工智能·前端框架·npm·软件供应链安全厂商·软件供应链安全工具
__lll_38 分钟前
Docker 从入门到实战:容器、镜像与 Compose 全攻略
前端·docker
木春1 小时前
react组件化思维:高复用性 UI 设计之道
前端·react.js