开篇
这是一篇前端小菜鸡关于省略号...的一些实现,话不多说,先来看看css的实现。
1 单行文本
1.1 css实现单行文本溢出
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
2 多行文本
2.1 css 实现多行文本溢出
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
text-overflow: ellipsis;
overflow: hidden;
注意:css 多行文本溢出省略需要加内核前缀
3 js实现
思路:获取显示文本内容的父级容器,如果超出限制高度,就删掉一位字符再比较,如果高度大于限制高度的三倍,我们就删掉后一半的字符再比较,直到等于限制高度。话不多说,上代码。
首先,为了方便测试,先生成长度为500的随机英文字符串,当然也可以是中文或者其他。
const random = () => {
return String.fromCharCode('a'.charCodeAt(0) + Math.random() * 26)
}
const getnerateStr = () => {
let s = '';
for (let i = 0; i < 500; i++) {
s += random()
}
return s
}
其次,往列表中插入10个随机生成的字符串
html
<div class="list"></div>
css
.item {
position: relative;
width: 500px;
line-height: 25px;
margin-bottom: 5px;
word-break: break-all;
background-color: rgba(200, 200, 200, 0.4);
}
.more {
display: inline-block;
}
.more a {
color: #1890ff;
cursor: pointer;
text-decoration: none;
}
js
const fragenment = document.createDocumentFragment()
for (let i = 0; i < 10; i++) {
const item = document.createElement('div')
item.classList.add('item')
let child = `
<span>${getnerateStr()}</span>
<div class="more">...</div>
`
item.innerHTML = child
fragenment.appendChild(item)
}
document.getElementsByClassName('list')[0].appendChild(fragenment)
如果不需要显示更多/收起删掉a标签即可。
现在根据思路来实现计算函数compute。
// wrapEle 外部容器 textEle 文本容器 line 要显示的行数
const compute = (wrapEle, textEle, line = 3) => {
// 必须传入容器
if(!wrapEle || !textEle) return;
const more = wrapEle.getElementsByClassName('more')[0]
// 限制高度=行高*行数 + 容器上下padding
const { lineHeight, paddingTop, paddingBottom } = getComputedStyle(wrapEle);
const padding = parseFloat(paddingTop) + parseFloat(paddingBottom);
const limitHeight = parseFloat(lineHeight) * line + padding
// 没有超出限制高度 隐藏...
if (wrapEle.clientHeight <= limitHeight) {
more.style.display = 'none'
return;
}
let text = textEle.innerText
const { floor } = Math
// 遍历1000 次,实际上不会执行到1000次
let n = 1000
while (n > 0 && wrapEle.clientHeight > limitHeight) {
if (wrapEle.offsetHeight > limitHeight * 3) {
// 大于限制高度的三倍,截取字符长度的一半
text = text.slice(0, floor(text.length / 2))
}else {
// 三倍以内一个个减掉
text = text.slice(0, text.length - 1)
}
textEle.innerText = text
n--;
}
}
// 调用compute
const items = [...document.getElementsByClassName('item')]
console.time('totalTime')
for (let i = 0; i < items.length; i++) {
const element = items[i];
const span = element.getElementsByTagName('span')[0]
compute(element, span)
}
console.timeEnd('totalTime')
效果
分析
10个长度为500字符串计算总耗时,在我的电脑上耗时410ms左右,感觉也还好;当插入的数量改为50个的时候,总耗时2590ms,页面刷新等待时长明显上升,那有没有办法解决呢?
首先分析是哪里耗时比较长,通过while循环去对文本容器更新,在大于限制高度三倍时,截取长度的一半;在小于或者等于三倍限制高度时,删除一个字符,进行下一次循环,直到等于限制高度。操作dom赋值的过程无法避免,那么是否可以减少循环的次数呢?
我的思路是:文本字符串的长度是固定的,那么只需要快速的找到刚好等于限制高度的位置,然后再一个一个的加上字符再判断是否超出限制高度就可以了。首先需要快速的找到等于限制高度的位置,我这里采用的是二分查找法。
二分法
let low = 0
let high = originText.length - 1 // originText为原始字符串
let mid = null
const symbol = '...'
// 二分法
while (low <= high) {
// 计算中间值
mid = low + floor((high - low) / 2)
textEle.innerText = originText.slice(0, mid + 1) + symbol
if (wrapEle.offsetHeight === limitHeight) {
break;
} else if (wrapEle.offsetHeight > limitHeight) {
high = mid - 1
} else {
low = mid + 1
}
}
// 一个一个添加字符
while (wrapEle.offsetHeight <= limitHeight) {
mid++;
textEle.innerText = originText.slice(0, mid + 1) + symbol
}
// 减掉一个字符
textEle.innerText = originText.slice(0, mid) + symbol
效果
分析
使用二分法50个长度为500的字符串计算总耗时在360ms左右,相比没有使用二分法速度快了7倍多。既然二分法这么高效,那么是否可以多进行几次二分,使最后的位置越靠近最后一行最右边呢?
多次二分
在二分法外层加上一层循环用于控制二分法的最大执行次数。
// 根据文本容器的宽度(这里是外层容器的宽度,包括了左右padding, 此处简化了)控制二分法的次数
let count = wrapEle.clientWidth <= 500 ? 5 : 10
while (count > 0) {
while (low <= high) {
mid = low + floor((high - low) / 2)
textEle.innerText = originText.slice(0, mid + 1) + symbol
if (wrapEle.clientHeight === limitHeight) {
break;
} else if (wrapEle.clientHeight > limitHeight) {
high = mid - 1
} else {
low = mid + 1
}
}
// 每次二分完成后,把[low+1, high]区间再进行二分
low = mid + 1
count--;
}
效果
可以看到效果是一样的,但是总的耗时在80ms左右,相比于一次二分速度快了4倍多,相比于没有二分速度快了44倍。在计算100个的情况下,总耗时也大概在200ms左右。
最后
在真实的场景中字符可能不会有那么长,需要计算的个数可能不会很多。把固定长度改为随机0-500以内的长度,总的耗时在50ms左右。compute计算函数同样适用于vue和react中。最最最最后,我是一个前端小菜鸡,希望大家多多指正。