封装超出显示省略号,鼠标浮入显示tooltip组件

3,659 阅读4分钟

实现一个内容超出显示省略号,并鼠标浮入显示tooltip,不超出的不显示tooltip组件

ps:该组件是基于element-plus,使用vue3最新的setup语法糖实现的。不清楚的大家可以根据我的思路用其他技术栈实现。

背景

项目中有很多地方有超出显示省略号,然后鼠标浮入显示tooltip的需求。在这之前,我发现项目中有些是鼠标浮入都显示tooltip,无关乎是否超出;还有一些甚至超出显示省略号,而没有加tooltip,也就是这种情况用户连完整信息都不清楚。我感觉这应该不是产品想要的效果,可能是之前需求太多,或者这个项目经手的人太多,导致没有注意到这种细微的功能。然后我趁着这版本迭代的空闲期整理了一下,找产品挨个对了一下,想统一和完善项目中这种功能。项目主要的技术栈是vue2,在项目中封装了一个组件。在这里,我想着用vue3+element-plus实现,来给大家整理思路,以及巩固和学习一下vue3新setup语法糖。

功能点

  • 超出显示省略号
  • 显示省略号的情况下,鼠标移入显示全部
  • 考虑不是纯文本的情况,即可自定义内容区
  • 考虑tooltip显示的内容可自定义

实现

  • 超出显示省略号 这个其实不用多说,我们直接用css来实现就好,就是常见老三样,加上宽度的限制。

    <template>
      <div class="content" :style="{width: props.width}">
        {{props.content}}
      </div>
    </template>
    <script setup lang="ts">
      // 定义props的类型
      interface props {
        content: string,
        width: string
      }
      // 使用withDefaults来给props赋默认值
      const props = withDefaults(defineProps<props>(), {
        content: '',
        width: ''
      })
    </script>
    <style>
      .content {
        overflow: hidden; 
        white-space: nowrap;
        text-overflow: ellipsis
      }
    </style>
    

    现在这样就实现了超出显示省略号。我们来调用看看效果: 在这里插入图片描述 组件调用代码

    <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
      // import HelloWorld from './components/HelloWorld.vue'
      import { reactive } from 'vue';
      import ShowTooltip from './showTooltip.vue';
      const content = reactive({
        data: '您好您好您好您好您好您好您好您好您好您好您好'
      })
    </script>
    
    <template>
      <ShowTooltip :content="content.data" width="200px"/>
    </template>
    

小插曲:不用setup语法糖不知道,用了之后感觉真香。组件自动注册,只需引入就好。不用再单独写setup return,模版直接能用。props也是有单独定义的接口,之前的context也分开了attrs和emit...大家有兴趣的还是可以去尝试尝试。好言归正传,继续我们的组件编写。

  • 显示省略号的情况下,鼠标移入显示全部

    • 先实现鼠标移入显tooltip,我们这儿tooltip就使用element-plus里的el-tooltip组件。

      <template>
        <el-tooltip
          effect="dark"
          :content="props.content"
          placement="top"
        >
          <div class="content" :style="{width: props.width}">
            {{props.content}}
          </div>
        </el-tooltip>
      </template>
      <script setup lang="ts">
        // 定义props的类型
        interface props {
          content: string,
          width: string
        }
        // 使用withDefaults来给props赋默认值
        const props = withDefaults(defineProps<props>(), {
          content: '',
          width: ''
        })
      </script>
      <style>
        .content {
          overflow: hidden; 
          white-space: nowrap;
          text-overflow: ellipsis
        }
      </style>
      

      现在只是实现了鼠标移入显示tooltip,并没有区分是否超出。 在这里插入图片描述

    • 实现超出才显示tooltip

      我们先思考一下,如何判断是否超出。 其实不难想到,我们可以利用内容的width和外层盒子的宽度作对比。当内容的宽度大于等于盒子的宽度是,就显示tooltip。我们可以利用span标签不受css样式影响,宽度是内容自动撑开的,这样我们可以将内容使用span标签包裹起来,然后计算宽度,话不多说,直接上代码。

        <template>
          <el-tooltip
            effect="dark"
            :content="props.content"
            placement="top"
            :disabled="isShow"
          >
            <div class="content" :style="{width: props.width}" @mouseover="isShowTooltip">
              <span ref="contentRef">{{props.content}}</span>
            </div>
          </el-tooltip>
        </template>
        <script setup lang="ts">
          import { ref } from 'vue'
          // 定义props的类型
          interface props {
            content: string,
            width: string
          }
          // 使用withDefaults来给props赋默认值
          const props = withDefaults(defineProps<props>(), {
            content: '',
            width: ''
          })
          // 使用isShow来控制tooltip是否显示
          let isShow = ref<boolean>(true)
          // 在span标签上定义一个ref
          const contentRef  = ref()
          const isShowTooltip = function (): void {
            // 计算span标签的offsetWidth与盒子元素的offsetWidth,给isShow赋值
            if(contentRef.value.parentNode.offsetWidth > contentRef.value.offsetWidth) {
              isShow.value = true
            } else {
              isShow.value = false
            }
          }
        </script>
        <style>
          .content {
            overflow: hidden; 
            white-space: nowrap;
            text-overflow: ellipsis
          }
        </style>
      

      到这里,已经能满足文本内容的显示了,我们也完成一半的工作了。

  • 考虑不是纯文本的情况,即可自定义内容区 其实到这一步就比较简单了,就是写插槽。

      <template>
        <el-tooltip
          effect="dark"
          :content="props.content"
          placement="top"
          :disabled="isShow"
        >
          <div class="content" :style="{width: props.width}" @mouseover="isShowTooltip">
            <span ref="contentRef">
              <slot name="content">{{props.content}}</slot>
            </span>
          </div>
        </el-tooltip>
      </template>
    

    调用

      <ShowTooltip :content="content.data" width="200px">
        <template v-slot:content>1212324323</template>
      </ShowTooltip>
    

    考虑将内容作为插槽主要是因为项目中存在:以tag的形式列举值,超出tooltip显示。这里我大概还原一下。

      <script setup lang="ts">
        // This starter template is using Vue 3 <script setup> SFCs
        // Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
        // import HelloWorld from './components/HelloWorld.vue'
        import { reactive } from 'vue';
        import ShowTooltip from './showTooltip.vue';
        const content = reactive({
          data: '您好您好您好您好您好您好您好您好您好您好您好'
        })
        const tags = reactive([
          '苹果',
          '梨子',
          '香蕉',
          '芒果',
          '火龙果',
          '猕猴桃'
        ])
      </script>
    
      <template>
        <!-- 单纯的文本 -->
        <ShowTooltip :content="content.data" width="200px"/>
      <br/>
        <!-- 将内容自定义 -->
        <ShowTooltip :content="content.data" width="200px">
          <template v-slot:content>
            <el-tag v-for="item in tags" :key="item" class="tag-item"> {{item}}</el-tag>
          </template>
        </ShowTooltip>
      </template>
    
      <style>
        .tag-item {
          margin-left: 5px;
        }
      </style>
    
    

    在这里插入图片描述

  • 考虑tooltip显示的内容可自定义 采取上面同样的办法,将自定义区域slot,并且兼容两个tooltipContent 与 content。直接上代码

      <template>
        <el-tooltip
          effect="dark"
          :content="props.tooltipContent ? props.tooltipContent : props.content"
          placement="top"
          :disabled="isShow"
        >
          <template #content>
            <!-- 此处的默认值先看tooltipContent有没有,没有就给默认content -->
            <slot name="tooltipContent">{{props.tooltipContent ? props.tooltipContent : props.content}}</slot>
          </template>
          <div class="content" :style="{width: props.width}" @mouseover="isShowTooltip">
            <span ref="contentRef">
              <!-- 给一个没有写插槽的默认值,兼容纯文本的情况 -->
              <slot name="content">{{props.content}}</slot>
            </span>
          </div>
        </el-tooltip>
      </template>
      <script setup lang="ts">
        import { ref } from 'vue'
        // 定义props的类型
        interface props {
          content: string,
          width: string,
          tooltipContent?: string
        }
        // 使用withDefaults来给props赋默认值
        const props = withDefaults(defineProps<props>(), {
          content: '',
          width: '',
          tooltipContent: ''
        })
        // 使用isShow来控制tooltip是否显示
        let isShow = ref<boolean>(true)
        // 在span标签上定义一个ref
        const contentRef  = ref()
        const isShowTooltip = function (): void {
          // 计算span标签的offsetWidth与盒子元素的offsetWidth,给isShow赋值
          if(contentRef.value.parentNode.offsetWidth > contentRef.value.offsetWidth) {
            isShow.value = true
          } else {
            isShow.value = false
          }
        }
      </script>
      <style>
        .content {
          overflow: hidden; 
          white-space: nowrap;
          text-overflow: ellipsis
        }
      </style>
    

    调用

      <ShowTooltip width="200px">
        <template v-slot:tooltipContent>
          <span>1223214234</span>
        </template>
        <template v-slot:content>
          <el-tag v-for="item in tags" :key="item" class="tag-item"> {{item}}</el-tag>
        </template>
      </ShowTooltip>
    

在这里插入图片描述

小结

这个组件我们主要依赖传入的几个属性,及抛出的slot灵活使用。

属性
  • width: 盒子的宽度,要使用超出显示...,该属性必填
  • content: 文本内容
  • tooltipContent: tooltip显示的文本内容,不传该属性时,显示content
slot
  • content: 内容插槽,自定义内容区域
  • tooltipContent: tooltip内容自定义区域
组件完整代码
<template>
  <el-tooltip
    effect="dark"
    :content="props.tooltipContent ? props.tooltipContent : props.content"
    placement="top"
    :disabled="isShow"
  >
    <template #content>
      <slot name="tooltipContent">{{props.tooltipContent ? props.tooltipContent : props.content}}</slot>
    </template>
    <div class="content" :style="{width: props.width}" @mouseover="isShowTooltip">
      <span ref="contentRef">
        <!-- 给一个没有写插槽的默认值,兼容纯文本的情况 -->
        <slot name="content">{{props.content}}</slot>
      </span>
    </div>
  </el-tooltip>
</template>
<script setup lang="ts">
  import { ref, useSlots } from 'vue'
  // 定义props的类型
  interface props {
    content?: string,
    width: string,
    tooltipContent?: string
  }
  // 使用withDefaults来给props赋默认值
  const props = withDefaults(defineProps<props>(), {
    content: '',
    width: '',
    tooltipContent: ''
  })
  // 使用isShow来控制tooltip是否显示
  let isShow = ref<boolean>(true)
  // 在span标签上定义一个ref
  const contentRef  = ref()
  const isShowTooltip = function (): void {
    // 计算span标签的offsetWidth与盒子元素的offsetWidth,给isShow赋值
    if(contentRef.value.parentNode.offsetWidth > contentRef.value.offsetWidth) {
      isShow.value = true
    } else {
      isShow.value = false
    }
  }
</script>
<style>
  .content {
    overflow: hidden; 
    white-space: nowrap;
    text-overflow: ellipsis
  }
</style>

题外话:大家可以根据需要基于此实现更多功能,比如点击触发,还是鼠标移入触发tooltip等