element table组件的show-overflow-tooltip属性的实现原理

前言

        element ui 组件库中 table-column 组件有一个 show-overflow-tooltip 属性,设置了这个属性后,当表格的单元格宽度不够时单元格内的文本内容会被截断同时在末尾增加一个省略号。show-overflow-tooltip 属性的功能是怎么实现的呢,我们来翻翻源码一探究竟,同时自己实现一个与该属性功能相近的 overflowTooltip 组件。

一、基础知识点

        实现 show-overflow-tooltip 属性功能的源码在这里,其中具体实现的代码在 handleCellMouseEnter 方法中。这里面涉及到了两个基础知识:scrollWidth 和 Range。

1、scrollWidth

        每一个 DOM 节点都有 scrollWidth 属性,这个只读属性表示的是节点中的内容宽度,包括由于 overflow 溢出而在屏幕上不可见的内容。scrollWidth 值等于元素在不使用水平滚动条的情况下在视口(viewport)中展示所有内容时所需的最小宽度。其测量方式与 clientWidth 相同:它包含元素的内边距(padding-left 和 padding-right),但不包括边框(border)、外边距(margin-left 和 margin-right)和垂直滚动条(如果存在)。 它还可以包括伪元素的宽度,例如::before::after。 如果元素的内容不需要水平滚动就可以完全展示出来,则其 scrollWidth 等于 clientWidth。

        这个属性会进行四舍五入并返回整数,如果你需要小数形式的值可以使用element.getBoundingClientRect()。

        所有浏览器都支持 scrollWidth 属性,但是在不同浏览器中获取到的值不相同。在实际测试过程中,谷歌获取的 Element.scrollWidth 和 IE、火狐下获取的 Element.scrollWidth  不相同。

2、Range

        调用 Document.createRange() 可以生成一个 Range 对象。一旦一个 Range 对象被建立,在使用它的大多数方法之前需要去设置它的临界点。示例如下。

var range = document.createRange();

range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);

         Range 对象表示的是 document 的一个片段即从开始节点到结束节点之间的一段内容(包括元素节点和文本内容)。还可以通过 Selection.getRangeAt() 和 Document.Range() 两种方法来生成 Range 对象。

        Range 对象的 setStart 方法可以设置 range 的起始位置。如果起始节点类型是 Text, Comment, or CDATASection之一, 那么 startOffset 指的是从起始节点算起字符的偏移量。 对于其他 Node 类型节点, startOffset 是指从起始结点开始算起子节点的偏移量。startOffset 必须为不小于0的整数。

        Range 对象的 setEnd 方法可以设置 range 的结束位置。如果结束节点类型是 Text, Comment, or CDATASection之一, 那么 endOffset 指的是从结束节点算起字符的偏移量。 对于其他 Node 类型节点, endOffset 是指从结束结点开始算起子节点的偏移量。endOffset 必须为不小于0的整数。

        另外,Range 对象有一个 getBoundingClientRect 方法。该方法可以返回一个 DOMRect 对象,该对象是一个将范围内所有元素的边界矩形包围起来的矩形(关于边界矩形,可以参考 Minimum Bouding Rectangles)。getBoundingClientRect 方法返回的矩形的详细信息与元素的 getBoundingClientRect 方法返回的值一致即元素的大小及其相对于视口的位置(如果是标准盒子模型,获取到的元素的尺寸等于width/height + padding + border-width的总和。如果box-sizing: border-box,获取到的元素的的尺寸等于 width/height)。

二、show-overflow-tooltip实现

        show-overflow-tooltip 的实现正是基于 scrollWidth 和 Range 的应用。下面看看源码以及其中的取舍。

        1、源码解析

handleCellMouseEnter(event, row) {
      const table = this.table;
      const cell = getCell(event);

      // ……省略部分源码

      // 判断是否text-overflow, 如果是就显示tooltip
      const cellChild = event.target.querySelector('.cell');

      // 判断是否启用了overflow-tooltip,即 el-tooltip 组件是否被渲染
      if (!(hasClass(cellChild, 'el-tooltip') && cellChild.childNodes.length)) {
        return;
      }

      // 使用 range 代替 scrollWidth 来判断文本是否溢出,这样做是为了解决潜在的 bug。
      // FireFox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1074543#c3
      const range = document.createRange();
      // 设置 range 的起点
      range.setStart(cellChild, 0);
      // 设置 range 的终点,因为起终点都在同一个节点上,所以设置终点偏移量以选中节点的内容
      range.setEnd(cellChild, cellChild.childNodes.length);
      // 获取节点的内容的宽度
      const rangeWidth = range.getBoundingClientRect().width;
      // 获取节点的左右padding
      const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
        (parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0);
      // 由于element ui 中把 box-sizing 设置为了 border-box,因此真实宽度需要加上padding
      // 节点的内容真实宽度大于节点宽度时显示tooltip
      if ((rangeWidth + padding > cellChild.offsetWidth || cellChild.scrollWidth > cellChild.offsetWidth) && this.$refs.tooltip) {
        // ……省略显示 tooltip 的源码
      }
    }

        2、为什么没有采取 scrollWidth

                通过 DOM 节点对象的 scrollWidth 属性获取到的 width 就是当前节点的内容的真实宽度,但是,源码中依然使用了 range 做了替代,为什么要这么做呢?这是因为在火狐浏览器的旧版本中存在一个 bug。

                 scrollWidth 属性在火狐浏览器 v32 版本中有 bug。当元素的 CSS 属性中使用了 text-overflow: ellipsis 和 box-sizing: border-box 时获取到的 scrollWidth 的值会比真实值偏小。具体 bug 信息可以查看这里

三、自定义overflow-tooltip组件

        清楚了 show-overflow-tooltip 属性功能的实现原理后我们可以利用这个原理自定义一个与业务无关并且脱离于其他组件的 overflow-tooltip 组件。实现也很简单,具体代码如下。

<template>
  <div :id="id" class="overflow-tooltip" :title="title" :style="{maxWidth:maxWidth}">
    {{ content }}
  </div>
</template>

<script>
export default {
  props: {
    content: [String, Number, Boolean],
    maxWidth: {
      type: String
    }
  },
  data() {
    return {
      title: '',
      id: Math.random().toString(36).slice(2)
    }
  },

  mounted() {
    const el = document.getElementById(this.id)
    const elComputed = document.defaultView.getComputedStyle(el, '')
    const padding =
      parseInt(elComputed.paddingLeft.replace('px', '')) +
      parseInt(elComputed.paddingRight.replace('px', ''))

    const range = document.createRange()
    range.setStart(el, 0)
    range.setEnd(el, el.childNodes.length)
    const rangeWidth = range.getBoundingClientRect().width

    if (
      rangeWidth + padding > el.offsetWidth ||
      el.scrollWidth > el.offsetWidth
    ) {
      this.title = this.content
    }
  }
}
</script>

<style scoped>
.overflow-tooltip {
  display: inline-block;
  width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  line-height: 1;
  vertical-align: middle;
}
</style>

        使用方式:

<OverflowTooltip :content="content" max-width="118px" />

参考文献:

         1、element ui table 组件源码:https://github.com/ElemeFE/element/blob/dev/packages/table/src/table-body.js

        2、Element.scrollWidth:https://developer.mozilla.org/zh-CN/docs/Web/API/element/scrollWidth

        3、Document.createRange():https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createRange

        4、Range:https://developer.mozilla.org/zh-CN/docs/Web/API/Range

  • 21
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仰望星空的代码

创作不易,您的支持是我的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值