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页面
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=>解压即可。