在实际项目工程中,大家经常会遇到类似需求------文字溢出,鼠标悬停显示title,反之,则取消悬停title效果。
如果用了组件库,那么在类似table等组件,确实内嵌类似功能,可以直接用。
反之,如果你自己写的组件,想要有上述效果,那么该怎么办?
给大家几个"万能方案",随取随用,并且我不会把代码写的过于冗余+过长,以大家看的舒适+便于移植为主。
如果你觉得适用,不妨关注西红柿,一起交流学习!那么下面直接开始正题吧!
一.对于市面tooltip的解决方案,目前有以下几种
toolTip的核心原理,就是找到"文字溢出的临界值",比如宽度,高度等等,你是要一行溢出效果,还是多行溢出,都有相应的解决办法。
1.第一种方法是"根据offsetWidth+scrollWidth 解决",这种方案属于传统方案,而且网上例子很多,而且部分博主封装成了自定义指令。缺点在于"代码量"太大,移植到你自己的项目中,需要花一点时间,优点在于"bug少+稳定"。
2.第二种方法算是下下策,根据字数进行限制。这里重点要区分文字和数字、字符的数量,因为十个汉字的位置,可以容纳更多数字,因此按照length来界定范围,还是会出bug,后面还得改。
3.第三种办法玩法很多,其实还是对比宽度、高度,拿到文字溢出的临界值,最后再去判断,当然我要讲的也是这一种。
二.toolTip demo移植方案
这一块我不考虑你目前用的什么"组件库",而是从demo层面,来实现原生title悬停显示效果。
如果你想更改"title"显示框的效果,那就把你所在组件库的toolTip拿出来,在外面包一层,再用我下面的"文字溢出判断方式",来判断即可。(如果看到这有点懵,问题不大,接着看,你会豁然开朗!)
再说一点,上述图片中的title显示效果,属于浏览器内置的css样式,无法改,并且不建议"硬改",否则会出现浏览器不兼容等疑难bug,想换颜色,换位置等等,你去组件库把toolTip拎出来包一层即可。
方法一:
先把我的代码放上
treeData是我的数据,因为里面有很多小模块,所以进行了循环。
第二层div加了鼠标事件(移入、移出),主要用来判断"文字溢出的临界点,到底是true还是false)
div里面包裹了两个span,{{item.classifyName}}是我要显示的文字内容。
v-if后面接的是一个布尔类型的开关 ,用以判断文字是否溢出,所以在template这里,已经很明显了,你可以按照你自己的项目,进行对照。
结构不是死的,根据实际情况去改。
上述图片代码奉上:
ini
<div
class="leftChild"
v-for="(item, index) in treeData"
:key="index"
@click="handleChild(item)"
style="font-weight: 600; font-size: 12px; padding: 0 20px"
>
<div
@mouseenter="e => isShowToltip(e, index)"
@mouseleave="hideTip(index)"
>
<span ref="treeDom" v-if="item.isShow" :title="item.classifyName">
{{ item.classifyName }}</span
>
<span ref="treeDom" v-else> {{ item.classifyName }}</span>
</div>
</div>
接下来要做的就是"判断文字溢出的临界点 ",重点是isShowToltip函数+hideTip函数,写完功能也就实现了。
先上图一睹为快!
解释一下isShowToltip函数:
第一步设置了一个开关bool,布尔类型,用来判断文字是否溢出。
第二步获取父元素的宽度,是在上图@mouseenter取得回调函数,e.target这个就不解释了,很简单。
重点说一下getBoundingClientRect
getBoundingClientRect()获取元素位置,这个方法没有参数
getBoundingClientRect()用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。
getBoundingClientRect()是DOM元素到浏览器可视范围的距离(不包含文档卷起的部分)。
该函数会返回一个Object对象,该对象有6个属性:top,lef,right,bottom,width,height
child.getBoundingClientRect().width就是获取当前元素的宽度。
咱们拿到外层div宽度后,再和里层span的宽度进行对照,结果就是"文字是否溢出"的开关(核心原理,是不是很简洁!)
回到正题,lastSpan指的是内层span的宽度,这里初始化为0,下一步会赋值。
Array.from(e.target.children).forEach((child, i) => { lastSpan = child.getBoundingClientRect().width console.log(e.target 的第 ${i} 个子元素宽度
, lastSpan) })
这一步有点说法,因为使用getBoundingClientRect的前提是获取一个dom,只有在dom后面,才能用。
而e.target.children是一个类数组对象,明显不符合,所以做了数组化(Array.from)
后面对其进行了循环赋值。循环的内容,其实就是里面的span,一般情况下,都是一个div包一个span,所以说到底其实就是在循环"这个title",你可以直接用。
到了下一步,其实父元素+子元素的实际宽度都拿到了,那么比较,再给开关赋值即可!
this.treeData[index].isShow = bool这一步,其实我把开关放到了"我个人的数据中",如果你和我的情况类似,可直接用(把treeData变量换成你的名字即可)。
反之,那么可以把开关的布尔值return返回,同样可以在模板中进行判断。(举一反三即可)
至于hideTip函数,这个就不用解释了,那就是鼠标移出,关闭开关(这一步可有可无,因为每次鼠标移入,都会重置开关)
那么第一种方法就完结撒花了,是不是很简单!!!
再说第二种方法,原理类似,先放template模板结构:
代码如下:
ini
<div
class="leftChild"
v-for="(item, index) in treeData"
:key="index"
@click="handleChild(item)"
style="font-weight: 600; font-size: 12px; padding: 0 20px"
>
<span
ref="treeDom"
v-if="isOverflowing(item.classifyName)"
:title="item.classifyName"
>
{{ item.classifyName }}</span
>
<span ref="treeDom" v-else> {{ item.classifyName }}</span>
</div>
这里我想重点说一下isOverflowing,也是最关键的实现点。
我先把实现图奉上:
代码示意:
ini
isOverflowing (text) {
const node = document.createElement('div')
node.style.position = 'absolute'
node.style.opacity = '0'
node.style.whiteSpace = 'nowrap'
node.innerText = text
document.body.appendChild(node)
const isOverflow = node.offsetWidth > 256
document.body.removeChild(node)
return isOverflow
},
isOverflowing方法的实现原理,同样是判断"文字溢出的临界点"。
这里面我新建一个dom------div,然后对其进行绝对定位+css样式设置。
接着把你的title赋值给它,也就是node.innerText = text这一步。
直白一点,现在我就是模拟一个dom,1比1精仿你的title及外层盒子。
把它加在html上,也就是document.body.appendChild(node)这一步。
然后拿到其offsetWidth数值,和我最外层盒子进行判断。
这里有人会有疑问,为什么要用offsetWidth?为什么不用scrollWidth?
首先我1:1精仿的盒子,并没有限制宽度,而是它的内容,会完全展示在div里,我拿offsetWidth,就是其title的总长度。
那么接下来const isOverflow = node.offsetWidth > 256这一步就很好理解了,256你自己换成自己外层盒子的宽度即可。
最后比对完,把精仿的div盒子removeChild移除掉即可。
函数最终返回值是布尔类型,用以判断title内容是否溢出,在template模板上v-if判断不同情况即可。
另外再讲一下对于title样式的设置。
可能有的人想换一种效果,比如:
其实同样很简单,拿你项目的配套toolTip组件,在最外面包一层即可,顺便把:title属性去掉,即可完成!
另外对于超出显示省略号的问题,在你的盒子上加上这三个属性就可以:
css
1. white-space: nowrap !important;
1. overflow: hidden !important;
1. text-overflow: ellipsis !important;
两种方法任选其一,直接移植在项目中,就很简单了!
最终效果就是这样: