自定义Tooltip 组件:根据内容长度判断是否需要提示信息
20220610更新
自定义该组件主要就为了解决一个问题:组件内部自动计算文本是否超宽来决定tooltip提示信息显示与否。很久以前写了一版,利用的是创建一个用户看不见的悬浮div做媒介来做比较。功能是实现了,但是多创建了节点消耗了性能。后面陆续写了好几个版本的优化,由于种种原因,没有更新博客。最近不经意间发现Element-ui的`el-table`组件的`show-overflow-tooltip`属性已经做到了只有出现省略号才显示提示信息。秉着助人为乐的精神(●>ω<●),在此将其抽离出来,分享给需要的朋友。
1.组件
// vp-tooltip.vue<template><divclass="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: {// 鼠标键入显示tooltiphandleCellMouseEnter(event) {// 无提示内容直接返回if (!this.content && !this.$slots.content) {return;}const cell = event.target;// 判断是否text-overflow, 如果是就显示tooltipconst 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#c3const 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);}},// 鼠标移出关闭tooltiphandleCellMouseLeave() {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=>解压即可。