自定义Tooltip 组件:根据内容长度判断是否需要提示信息

20220610更新 


       自定义该组件主要就为了解决一个问题:组件内部自动计算文本是否超宽来决定tooltip提示信息显示与否。很久以前写了一版,利用的是创建一个用户看不见的悬浮div做媒介来做比较。功能是实现了,但是多创建了节点消耗了性能。后面陆续写了好几个版本的优化,由于种种原因,没有更新博客。最近不经意间发现Element-ui的`el-table`组件的`show-overflow-tooltip`属性已经做到了只有出现省略号才显示提示信息。秉着助人为乐的精神(●>ω<●),在此将其抽离出来,分享给需要的朋友。

1.组件

// vp-tooltip.vue

<template>
  <div
    class="vp-tooltip"
    @mouseenter="handleCellMouseEnter"
    @mouseleave="handleCellMouseLeave"
  >
    <div :class="classObj" :style="styleObj">
      <slot></slot>
    </div>
    <el-tooltip ref="tooltip" placement="top" :content="tooltipContent">
      <template v-if="$slots.content" #content>
        <slot name="content"></slot>
      </template>
    </el-tooltip>
  </div>
</template>

<script>
import debounce from "throttle-debounce/debounce";
import { getStyle, hasClass } from "element-ui/src/utils/dom";

/**
 * 显隐可控的tooltip组件
 * @desc 基于element-ui抽离,组件内部自动计算文本是否超宽来决定tooltip调用与否
 * @author zhengvipin
 * @tips 个人业务组件,仅供演示使用
 * @date 2022/06/10 12:57
 */
export default {
  name: "VpTooltip",
  componentName: "VpTooltip",
  props: {
    // 限定超宽范围
    width: {
      String,
      default: "150px",
    },
    // tooltip显示的内容
    content: String,
    // cell自定义样式
    cellStyle: {
      type: Object,
      default: () => ({}),
    },
    // 保留属性,意义不大,纯粹为了与源码结构保持一致
    showOverflowTooltip: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      tooltipContent: "",
    };
  },
  computed: {
    classObj() {
      return {
        cell: true,
        "el-tooltip": this.showOverflowTooltip,
      };
    },
    styleObj() {
      return {
        ...this.cellStyle,
        width: this.width,
      };
    },
  },
  created() {
    // 防抖函数保护,避免鼠标键入后n秒内多次调用
    this.activateTooltip = debounce(50, (tooltip) =>
      tooltip.handleShowPopper()
    );
  },
  methods: {
    // 鼠标键入显示tooltip
    handleCellMouseEnter(event) {
      // 无提示内容直接返回
      if (!this.content && !this.$slots.content) {
        return;
      }
      const cell = event.target;
      // 判断是否text-overflow, 如果是就显示tooltip
      const cellChild = event.target.querySelector(".cell");
      if (!(hasClass(cellChild, "el-tooltip") && cellChild.childNodes.length)) {
        return;
      }
      // use range width instead of scrollWidth to determine whether the text is overflowing
      // to address a potential FireFox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1074543#c3
      const range = document.createRange();
      range.setStart(cellChild, 0);
      range.setEnd(cellChild, 1);
      const rangeWidth = range.getBoundingClientRect().width;
      const padding =
        (parseInt(getStyle(cellChild, "paddingLeft"), 10) || 0) +
        (parseInt(getStyle(cellChild, "paddingRight"), 10) || 0);
      // 判断文本 + padding的宽度是否超过容器宽度
      if (
        (rangeWidth + padding > cellChild.offsetWidth ||
          cellChild.scrollWidth > cellChild.offsetWidth) &&
        this.$refs.tooltip
      ) {
        const tooltip = this.$refs.tooltip;
        // TODO 会引起整个 Table 的重新渲染,需要优化
        this.tooltipContent = this.content; // 这里为啥写这行类似1=1的代码呢,纯粹为了与源码结构保持一致
        tooltip.referenceElm = cell;
        tooltip.$refs.popper && (tooltip.$refs.popper.style.display = "none");
        tooltip.doDestroy();
        tooltip.setExpectedState(true);
        this.activateTooltip(tooltip);
      }
    },
    // 鼠标移出关闭tooltip
    handleCellMouseLeave() {
      const tooltip = this.$refs.tooltip;
      if (tooltip) {
        tooltip.setExpectedState(false);
        tooltip.handleClosePopper();
      }
    },
  },
};
</script>

<style lang="scss">
// 这里只是为了演示,实际编写个人组件库样式不写在这
// 具体参考个人博客:https://blog.csdn.net/zhengvipin/article/details/125187489
.vp-tooltip .cell {
  box-sizing: border-box;
  word-break: break-all;
  padding-left: 10px;
  padding-right: 10px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
</style>

2.测试

// App.vue

<template>
  <div id="app">
    <div class="container">
      <el-form label-suffix=":" label-width="200px" label-position="left">
        <el-form-item label="文本未超宽">
          <vp-tooltip :content="content">{{ content }}</vp-tooltip>
        </el-form-item>
        <el-form-item label="文本超宽">
          <vp-tooltip :content="content2">{{ content2 }}</vp-tooltip>
        </el-form-item>
        <el-form-item label="更多 Content">
          <vp-tooltip>
            {{ content3 }}
            <div slot="content">多行信息<br />第二行信息</div>
          </vp-tooltip>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>

<script>
import VpTooltip from "./components/tooltip";
export default {
  components: {
    VpTooltip,
  },
  data: function () {
    return {
      content: "上海市普陀区",
      content2: "上海市普陀区金沙江路 1519 弄",
      content3: "上海市普陀区金沙江路 1519 弄",
    };
  },
};
</script>

<style lang="scss" scoped>
#app {
  height: calc(100vh - 16px);
  position: relative;
  .container {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);

    ::v-deep .el-form-item__label,
    ::v-deep .el-form-item__content {
      line-height: 28px;
    }

    .vp-tooltip {
      border: 1px solid #dcdfe6;
      cursor: pointer;
    }
  }
}
</style>

3.截图

 

PS:图包就不提供了,年代太久远,忘记怎么做了  (*^__^*)。


20190102旧版


    elementUI中的Tooltip组件是用于展示鼠标 hover 时的提示信息,类似于原生html的title属性。我们实际开发中一般还会在此基础上提几个需求:(1).自定义显示文本行数 (2).文本超出行数,以省略号代替,并在鼠标 hover 出现提示信息 (3).文本过长时省略后,提供相应的快捷复制功能。基于这些原因,在此对elementUI之Tooltip组件进行了简单定制化。

目录

1.demo页面

2.Javascript核心代码 

3.demo演示

4.实现原理简述


1.demo页面

kt-tooltip-demo.html
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
          name="viewport">
    <meta content="ie=edge" http-equiv="X-UA-Compatible">
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        /*  限制tooltip最大宽度 */
        .el-tooltip__popper {
            max-width: 500px;
        }
    </style>
    <title>Document</title>
</head>
<body>

<div id="app" v-cloak>
    <el-card style="padding: 20px;">
        <div slot="header">KT-TOOLTIP DEMO</div>
        <h1 style="font-weight: 900;">不出现省略号时,tooltip不显示:</h1>
        <kt-tooltip :value="value1" style="border: 1px solid red;" width="500px"></kt-tooltip>
        <br>
        <h1 style="font-weight: 900;">出现省略号时,tooltip显示,可一键复制:</h1>
        <kt-tooltip :rows="2" :value="value2" style="border: 1px solid red;" width="500px" :clip="true"></kt-tooltip>
        <br>
        <h1 style="font-weight: 900;">可设置label:</h1>
        <kt-tooltip :value="value1" label="备注:" style="border: 1px solid red;" width="500px"></kt-tooltip>
    </el-card>
</div>
</body>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="kt-tooltip.js"></script>
<script>
    const APP = new Vue({
        el: "#app",
        data() {
            return {
                value1: '2016年1月15日,报案人XXX到我派出所报案。报报报报报报报报报。',
                value2: '2016年01月04日12时20分接事主XXX(女,广东省XX市XX区XX路XX号X座XX房,44XX02XXX108XX09XX)报称:其2016年01月03日23时至04日10时,在XX省XX市XX区XXX地下停车场0XX车位,一辆X色XX牌小车的车头盖,左前后门,右前后门,后尾门被刮花了,车牌:EXX242,车主:XXX。维修大约50000元左右。'
            }
        }
    })
</script>
</html>

2.Javascript核心代码 

kt-tooltip.js
document.write("<script src='clipboard.min.js'></script>");

Vue.component("kt-tooltip", {
    template: `
        <div :style="{'width':width}">
            <div class="kt-tooltip-input__hidden" style="position: fixed; left: -19800px;"><span>{{ label }}</span>{{ value }}</div>
            <div v-if="flag"
                 @mouseenter="visibilityChange($event)"
                 style="cursor:pointer;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical"
                 :style="{'-webkit-line-clamp':rows,'color':color}">
                 <span style="color: #999;cursor: pointer;">{{ label }}</span>{{ value }}
            </div>
            <el-tooltip :placement="placement" v-else>
                <div slot="content">
                    <div>{{ value }}</div>
                    <p v-if="clip" style="text-align: right;"><a href="javascript:void 0" :data-clipboard-text="value" @mouseenter="handlerClipShow($event)">一键复制</a></p>
                </div>
                <div style="cursor:pointer;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical" :style="{'-webkit-line-clamp':rows}">
                    <span style="color: #999;cursor: pointer;">{{ label }}</span>{{ value }}
                </div>
            </el-tooltip>
        </div>
    `,
    data() {
        return {
            flag: true
        }
    },
    methods: {
        visibilityChange(event) {
            let ev = event.target;
            let thisWidth = ev.offsetWidth; // 元素的宽度
            let wordWidth = $(ev).prev('.kt-tooltip-input__hidden')[0].scrollWidth; // 文本内容的宽度
            if (wordWidth / thisWidth > this.rows) {
                this.flag = false;
            }
        },
        handlerClipShow(event) {
            const clipboard = new Clipboard(event.target);
            clipboard.on('success', (e) => {
                this.$message({
                    message: '复制成功',
                    type: 'success'
                });
                e.clearSelection();
            });
        }
    },
    props: {
        label: {
            type: String,
            required: false
        },
        value: {
            type: String,
            required: true
        },
        width: {
            type: String,
            default: '100%'
        },
        rows: {
            type: Number,
            default: 1
        },
        color: {
            type: String,
            required: false
        },
        placement: {
            type: String,
            default: 'top'
        },
        clip: {
            type: Boolean,
            default: false
        }
    }
});

3.demo演示

4.实现原理简述

1.同时布局三个文本内容div展示区
2.第一个文本内容区,即div1,仅用于计算要盛放的文本宽度,自身不呈现,此处采用绝对定位不占位隐藏;
3.第二个文本内容区,即div2,是一个比较媒介,实际呈现,当由div1得出的文本宽度超出div2宽度时,div2隐藏,div3显示,否则一直呈现div2;
4.第三个文本内容区,即div3,是一个具体含有el-tooltip组件的候选区,由div1与div2的比较结果决定显隐。
PS:完整demo以图包方式存放,右键 演示demo.gif 存储=》重命名xxx.rar=>解压即可。

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

zhengvipin

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值