判断文本是否溢出,并且溢出后鼠标悬浮展示tooltip

635 阅读1分钟

在Element plus的基础上封装一个超出时有tip,未超出时无tip的组件

EllipsisTooltip.vue

<template>
  <el-tooltip ref="tooltipRef" :content="content" :disabled="!isOverflow">
    <template #content>
      <slot name="content"/>
    </template>
    <div ref="triggerDom" class="tooltip-trigger" :class="{'single-line': lines === 1}">
      <!-- 默认使用content作为内容和tip,大多数场景下它们都是相同的 -->
      <span><slot>{{ content }}</slot></span>
    </div>
  </el-tooltip>
</template>

<script setup lang="ts">
import {onMounted, onUnmounted, ref} from "vue";

const props = withDefaults(defineProps<{
  content?: string,
  lines?: number, // 超出行数
}>(), {lines: 1})

const triggerDom = ref<HTMLDivElement>()
const isOverflow = ref(false)
// 使用ResizeObserver API观察尺寸变化,相比使用鼠标事件或window.resize的方式性能更好
const observer = new ResizeObserver(() => {
  if (triggerDom.value) {
    // 创建一个选区,等同于鼠标在页面上选择文本
    const range = document.createRange()
    range.setStart(triggerDom.value, 0)
    range.setEnd(triggerDom.value, triggerDom.value.childNodes.length)
    // 获得选区的尺寸
    const rect = range.getBoundingClientRect()
    if (props.lines === 1) {
      // 单行看宽度
      isOverflow.value = rect.width > triggerDom.value.offsetWidth
    } else {
      // 多行看高度
      isOverflow.value = rect.height > triggerDom.value.offsetHeight
    }
  }
})

onMounted(() => {
  // 观测dom的尺寸变化
  observer.observe(triggerDom.value)
})

onUnmounted(() => {
  // 取消所有观测
  observer.disconnect()
})
</script>

<style scoped>
/* 为了方便,这里使用了最新的CSS嵌套语法,兼容性很不好,不要在生产使用。你可以使用scss或者直接展开它们 */
.tooltip-trigger {
  text-overflow: ellipsis;
  overflow: hidden;

  &.single-line {
   white-space: nowrap;
  }

  &:not(.single-line) {
   display: -webkit-box;
   -webkit-line-clamp: v-bind(lines);
   -webkit-box-orient: vertical;
  }
}
</style>

使用示例

App.vue

<script setup lang="ts">
import EllipsisTooltip from "./components/EllipsisTooltip.vue";

</script>

<template>
  <div style="border: 1px solid red;width: 80px">
    <div style="width: 80px">
      <!-- 未超出 -->
      <EllipsisTooltip content="一段文本"/>
    </div>
    <div style="width: 80px;margin-top: 24px">
      <!-- 单行超出 -->
      <EllipsisTooltip content="一段挺长挺长的文本"/>
    </div>
    <div style="width: 80px;margin-top: 24px">
      <!-- 多行超出 -->
      <EllipsisTooltip :lines="2" content="一段挺长挺长的文本一段挺长挺长的文本"/>
    </div>
    <div style="width: 80px;margin-top: 24px">
      <!-- tip富文本 -->
      <EllipsisTooltip :lines="2" content="一段挺长挺长的文本一段挺长挺长的文本">
        <template #content>
          <div>甚至<b>可以</b><span style="color: red">带点花样</span></div>
        </template>
      </EllipsisTooltip>
    </div>
    <div style="width: 80px;margin-top: 24px">
      <!-- content富文本 -->
      <EllipsisTooltip :lines="2" content="使用插槽也不会有什么问题">
        <span>这段<b style="color: red;margin: 8px">文文本本</b>将会超出两行的长度</span>
      </EllipsisTooltip>
    </div>
  </div>
</template>

效果:

image.png

image.png

最后

  • 使用ResizeObserver API观测尺寸变化,减少计算频率。
  • 当组件外部容器为flex/grid等会影响内容尺寸的布局时,计算可能出错,因为flex默认会撑满容器,这时需要添加css样式消除影响。

兼容性还行:caniuse.com -webkit-line-clamp

image.png