vue3中教你如何使用指令解决文本的溢出提示

在我们项目开发中,经常会有超长文本溢出提示,未溢出则不提示的场景。
在项目开发中遇到了比较复杂的场景,在一个组织树中,我们使用了el-tree来显示组织树,文字长度不一,太长的显示不全,刚开始我们使用滚动条,结果不好看
后来我们就直接再el-tree中添加el-tooltip,发现没啥问题鼠标移入正常提示,但是再往下滚动的时候发现,页面会卡顿,因为每个el-tree都会产生一个el-tooltip,
只不过是通过v-if进行显示和隐藏的,频繁显示性能开销很大,性能极差,页面中还会出现一堆的 注释
想着开发一个指令,与el-tooltip类似,超出显示…移入显示内容

如下图所示,
请添加图片描述

<script lang="ts" setup>

import {ref} from "vue";

const treeData = ref([
  {
    "id": "65118224801137459281926519380023",
    "title": "测试",
    "level": 1,
    "parentId": "464064658652315648",
    "type": "PROJECT",
    "enable": 1,
    "list": [
      {
        "id": "65141629137928601691056522250367",
        "title": "未超出长度",
        "level": 2,
        "parentId": "65118224801137459281926519380023",
        "type": "PROJECT",
        "enable": 1,
        "list": []
      },
      {
        "id": "65141629137928601691056522250368",
        "title": "超出隐藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏",
        "level": 2,
        "parentId": "65118224801137459281926519380023",
        "type": "PROJECT",
        "enable": 1,
        "list": []
      },
      {
        "id": "65141629137928601691056522250369",
        "title": "超出隐藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏",
        "level": 2,
        "parentId": "65118224801137459281926519380023",
        "type": "PROJECT",
        "enable": 1,
        "list": []
      }
    ]
  },
])

const defaultProps = {
  children: 'list',
  label: 'title',
}


const tooltipTitle = ref("")
const showTitle = ref(true)

const onShowNameTipsMouseenter = (e) => {
  let target = e.target;
  let textLength = target.clientWidth;
  let containerLength = target.scrollWidth;
  if (textLength < containerLength) {
    tooltipTitle.value = e.target.innerText;
    showTitle.value = false;
  } else {
    showTitle.value = true;
  }
}

</script>

<template>
  <el-tree
      :data="treeData"
      :props="defaultProps"
      style="width: 150px"
  >
    <template #default="{ node }">
      <el-tooltip
          :content="tooltipTitle"
          :disabled="showTitle"
          effect="dark"
          placement="top"
      >
      <span
          class="span-ellipsis"
          @mouseover="onShowNameTipsMouseenter"
      >{{ node.label }}</span
      >
      </el-tooltip>
    </template>
  </el-tree>
</template>

<style lang="scss" scoped>
.span-ellipsis {
  width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  margin-right: 20px;
}
</style>

这样的代码比较复杂,而且复用性极低。如果在其他页面也有类似的场景,我们就不得不cv了

指令开发

如何判断是否溢出

这也算是一个知识点,首先我们需要判断文本是否溢出了节点。如何解决文本是否溢出,在element-ui中el-table有一个文本溢出可以隐藏,所以我再其源码中找到了这个element-ui/packages/table/src/table-body.js,判断溢出的方法,代码如下

const range = document.createRange();
range.setStart(cellChild, 0);
range.setEnd(cellChild, cellChild.childNodes.length);
const rangeWidth = range.getBoundingClientRect().width;
const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
    (parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0);
if ((rangeWidth + padding > cellChild.offsetWidth || cellChild.scrollWidth > cellChild.offsetWidth) && this.$refs.tooltip) {
    // ...
}

我们稍微改造一下,拿来用

实现溢出指令

function getElStyleAttr(element, attr) {
    const styles = window.getComputedStyle(element)
    return styles[attr]
}

const isOverflow = (target) => {
    const scrollWidth = target.scrollWidth
    const offsetWidth = target.offsetWidth
    const range = document.createRange()
    range.setStart(target, 0)
    range.setEnd(target, target.childNodes.length)
    const rangeWidth = range.getBoundingClientRect().width
    const padding = (parseInt(getElStyleAttr(target, 'paddingLeft'), 10) || 0) + (parseInt(getElStyleAttr(target, 'paddingRight'), 10) || 0)
    return (rangeWidth + padding > target.offsetWidth) || scrollWidth > offsetWidth
}
export const ellipsisTooltip = {
    mounted(el, binding) {
        // 避免用户遗漏样式,我们必须强制加上超出...样式
        el.style.overflow = 'hidden'
        el.style.textOverflow = 'ellipsis'
        el.style.whiteSpace = 'nowrap'

        const onMouseEnter = (e) => {
            if (isOverflow(el)) {
                console.log('溢出了')
            } else {
                console.log('未溢出')
            }
        }
        el.addEventListener('mouseenter', onMouseEnter)
    }
}

看下效果吧
请添加图片描述

如何把溢出的节点挂载到el-tooltip上

我们不直接像el-tooltip挂载到每个文本上面,这样就和el-tooltip没啥区别
我们直接把它添加在全局body下,而且每次直会显示一个,移出就隐藏删除添加的tooltip

首先我们需要准备好组件

MyTooltip.vue

<template>
  <!-- 指示 -->
  <transition name="el-fade-in-linear">
    <div v-show="tooltipShow" :style="tooltipStyle" class="wq-tooltip"
    >
      <span class="wq-tooltip-text" v-text="text"></span>
      <div :class="[
          {'left':placements === 'left'},
          {'bottom':placements==='bottom'},
          {'right':placements==='right'},
          {'top':placements==='top'}]" class="wq-tooltip-arrow"></div>
    </div>
  </transition>
</template>

<script>
import {ref, computed, onMounted} from 'vue'

export default {
  setup() {

    // 显示弹框
    const tooltipShow = ref(false);

    // 提示内容
    const text = ref()

    // 方向
    const placements = ref('left')

    // 显示
    function showTip() {
      tooltipShow.value = true
    }
    //设置提示内容
    function setContent(content) {
      text.value = content
    }
    //隐藏
    function hiddenTip() {
      tooltipShow.value = false
    }

    // 位置
    const tooltipPosition = ref({
      x: 0,
      y: 0
    })
    const tooltipStyle = computed(() => {
      return {
        transform: `translate3d(${tooltipPosition.value.x}px,${tooltipPosition.value.y}px,0)`
      }
    })
    return {
      tooltipShow,
      showTip,
      hiddenTip,
      setContent,
      tooltipPosition,
      tooltipStyle,
      text,
      placements,
    }
  }
}
</script>

<style lang="scss" scoped>
// tooltip
.wq-tooltip {
  padding: 10px;
  font-size: 12px;
  line-height: 1.2;
  min-width: 10px;
  word-wrap: break-word;
  position: fixed;
  left: 0;
  top: 0;
  background: #303133;
  color: #fff;
  z-index: 1000;
  display: block;
  border-radius: 8px;
  font-weight: 500;
  pointer-events: none;
}

// 小箭头
.wq-tooltip-arrow {
  position: absolute;
  width: 0;
  height: 0;
  border-width: 8px;
  border-style: solid;
}

// 如果在左侧
.wq-tooltip-arrow.left {
  border-color: transparent transparent transparent #303133;
  right: -15px;
  top: 50%;
  transform: translate3d(0, -50%, 0);
}

// 如果在下侧
.wq-tooltip-arrow.bottom {
  top: -15px;
  border-color: transparent transparent #303133 transparent;
  left: 50%;
  transform: translate3d(-50%, 0, 0);
}

// 如果在右侧
.wq-tooltip-arrow.right {
  left: -15px;
  top: 50%;
  transform: translate3d(0, -50%, 0);
  border-color: transparent #303133 transparent transparent;
}

// 如果在上侧
.wq-tooltip-arrow.top {
  bottom: -15px;
  border-color: #303133 transparent transparent transparent;
  left: 50%;
  transform: translate3d(-50%, 0, 0);
}

/* 动画 */
.tooltip-enter-from,
.tooltip-leave-to {
  opacity: 0;
  transition: opacity .3s ease;
}

.tooltip-leave-from,
.tooltip-enter-to {
  transition: opacity .1s ease;
}
</style>

在指令js中鼠标移入时挂载该组件,移出时销毁组件

directive.js

// 引入组件
import {createApp, nextTick} from "vue";
import MyToolTip from './MyToolTip.vue'

// 位置定位
function calculationLocation(el, target, placements) {
    if (!el || !target) return;
    el.tooltipPosition.y = 0;
    el.tooltipPosition.x = 0;
    let el_dom = el.$el.nextElementSibling.getBoundingClientRect()
    let target_dom = target.getBoundingClientRect()

    if (placements === "left") {
        el.tooltipPosition.x = target_dom.x - el_dom.width - 10
        el.tooltipPosition.y = target_dom.y - el_dom.height / 2 + target_dom.height / 2
    } else if (placements === "bottom") {
        el.tooltipPosition.x = target_dom.x + target_dom.width / 2 - el_dom.width / 2
        el.tooltipPosition.y = target_dom.y + el_dom.height + 10
    } else if (placements === "right") {
        el.tooltipPosition.x = target_dom.x + target_dom.width + 10
        el.tooltipPosition.y = target_dom.y - el_dom.height / 2 + target_dom.height / 2
    } else if (placements === "top") {
        el.tooltipPosition.x = target_dom.x + target_dom.width / 2 - el_dom.width / 2
        el.tooltipPosition.y = target_dom.y - el_dom.height - 10
    }
}

// 方向
const allPlacements = ['left', 'bottom', 'right', 'top']


function getElStyleAttr(element, attr) {
    const styles = window.getComputedStyle(element)
    return styles[attr]
}

const isOverflow = (target) => {
    const scrollWidth = target.scrollWidth
    const offsetWidth = target.offsetWidth
    const range = document.createRange()
    range.setStart(target, 0)
    range.setEnd(target, target.childNodes.length)
    const rangeWidth = range.getBoundingClientRect().width
    const padding = (parseInt(getElStyleAttr(target, 'paddingLeft'), 10) || 0) + (parseInt(getElStyleAttr(target, 'paddingRight'), 10) || 0)
    return (rangeWidth + padding > target.offsetWidth) || scrollWidth > offsetWidth
}

export const ellipsisTooltip = {
    mounted(el, binding) {
        //获取指令的参数
        const {
            value: {
                placement, content, destroyOnLeave
            } = {}
        } = binding;
        // 加上超出...样式
        el.style.overflow = "hidden";
        el.style.textOverflow = "ellipsis";
        el.style.whiteSpace = "nowrap";
        //鼠标移开时 清除元素
        const onMouseLeave = () => {
            if (el.w_tipInstance) {
                el.w_tipInstance.hiddenTip()
                el.w_tooltip.remove()
                el.w_tipInstance = null
                el.w_tooltip = null

            }
        };
        const onMouseEnter = () => {
            // 判断内容长度 需要展示
            if (isOverflow(el)) {
                const directiveList = allPlacements.filter(placement => binding.modifiers[placement])
                const placements = directiveList.length ? directiveList : allPlacements
                if (!el.w_tooltip) {
                    // 创建tooltip实例
                    const vm = createApp(MyToolTip)
                    // 创建根元素
                    el.w_tooltip = document.createElement('div')
                    // 挂载到页面
                    document.body.appendChild(el.w_tooltip)
                    el.w_tooltip.id = `tooltip_${Math.floor(Math.random() * 10000)}`
                    el.w_tipInstance = vm.mount(el.w_tooltip)
                }
                // 设置 tooltip 显示方向
                el.w_tipInstance.placements = placement || placements[0] || 'top'
                // 设置显示内容
                el.w_tipInstance.setContent(content || el.innerText)
                // 使 tooltip 显示
                el.w_tipInstance.showTip()
                nextTick(() => {
                    // 计算 tooltip 在页面中的位置
                    calculationLocation(el.w_tipInstance, el, placements[0])
                })
                el._scrollHandler = () => {
                    // 重新定位位置
                    if (el.w_tipInstance && el.w_tipInstance.tooltipShow) calculationLocation(el.w_tipInstance, el, placements[0])
                }
                window.addEventListener('scroll', el._scrollHandler)
                const _destroyOnLeave = destroyOnLeave || true
                if (_destroyOnLeave) el.addEventListener("mouseleave", onMouseLeave);
            }
        };
        el.addEventListener("mouseenter", onMouseEnter);
    },
    unmounted(el) {
        if (el.w_tooltip) {
            document.body.removeChild(el.w_tooltip)
        }
        window.removeEventListener('scroll', el._scrollHandler)
    }
}

指令和组件都好了,我们就在全局配置指令即可

import App from './App.vue'
import {ellipsisTooltip} from './directive.js'

const app = createApp(App);
app.directive('ellipse-tooltip', ellipsisTooltip);
app.mount('#app');

配置好全局指令后,页面中就可以直接使用了

<script lang="ts" setup>

import {ref} from "vue";

const treeData = ref([
  {
    "id": "65118224801137459281926519380023",
    "title": "测试",
    "level": 1,
    "parentId": "464064658652315648",
    "type": "PROJECT",
    "enable": 1,
    "list": [
      {
        "id": "65141629137928601691056522250367",
        "title": "未超出长度",
        "level": 2,
        "parentId": "65118224801137459281926519380023",
        "type": "PROJECT",
        "enable": 1,
        "list": []
      },
      {
        "id": "65141629137928601691056522250368",
        "title": "超出隐藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏",
        "level": 2,
        "parentId": "65118224801137459281926519380023",
        "type": "PROJECT",
        "enable": 1,
        "list": []
      },
      {
        "id": "65141629137928601691056522250369",
        "title": "超出隐藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏藏",
        "level": 2,
        "parentId": "65118224801137459281926519380023",
        "type": "PROJECT",
        "enable": 1,
        "list": []
      }
    ]
  },
])

const defaultProps = {
  children: 'list',
  label: 'title',
}


const tooltipTitle = ref("")
const showTitle = ref(true)

const onShowNameTipsMouseenter = (e) => {
  let target = e.target;
  let textLength = target.clientWidth;
  let containerLength = target.scrollWidth;
  if (textLength < containerLength) {
    tooltipTitle.value = e.target.innerText;
    showTitle.value = false;
  } else {
    showTitle.value = true;
  }
}

</script>

<template>
  <el-tree
      :data="treeData"
      :props="defaultProps"
      style="width: 150px"
  >
    <template #default="{ node }">
      <!--      <el-tooltip-->
      <!--          :content="tooltipTitle"-->
      <!--          :disabled="showTitle"-->
      <!--          effect="dark"-->
      <!--          placement="top"-->
      <!--      >-->
      <span v-ellipse-tooltip.top
            class="span-ellipsis"
            @mouseover="onShowNameTipsMouseenter"
      >{{ node.label }}</span
      >
      <!--      </el-tooltip>-->
    </template>
  </el-tree>
</template>

<style lang="scss" scoped>
.span-ellipsis {
  width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  margin-right: 20px;
}
</style>

看看效果
请添加图片描述

到这里我们的ellipse-tooltip指令就完成了,还有很大改进的地方,需要大佬们自行去完善了

  • 10
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值