![9a29ffeade17a627a72f6863ae9cbf2a.png](https://img-blog.csdnimg.cn/img_convert/9a29ffeade17a627a72f6863ae9cbf2a.png)
文本截断
视觉效果添加...后缀
文本截断,遇到的场景有单行和多行,一般还是单行居多。
处理的方法,也分JavaScrip和CSS两类。
我们来细细看下
JavaScript方法
处理单行
JavaScript方法处理单行文本溢出,是最最基本的。
它首先是根据单行宽度,设定一个 标准字长 ,和 我们目标的文本字长做比较,判断文本有没有溢出。操作的是str#length
。
优势在于简便、无兼容问题,劣势在于对于字母、数字、中文等的视觉效果有不一致。
粗糙场景适用。
处理多行
JavaScript方法处理多行文本溢出,也是最近看到的一种方法,demo-jsfiddle-multilines-cut。
JavaScript代码:
const getNumInfo = (value) => +value.slice(0, -2)
//截断多行文本
const truncateMultiLinesText = (selectors, rows = 3, fix = -3) => {
//取目标元素
const ele = document.querySelector(selectors);
//取需要信息
const text = ele.innerText;
const totalTextLen = text.length;
const lineWidth = getNumInfo(window.getComputedStyle(ele).width);
const fontSize = getNumInfo(window.getComputedStyle(ele).fontSize)
// 计算:单行字数、多行字数
const strNum = Math.floor(lineWidth / fontSize);
const totalStrNum = Math.floor(strNum * rows);
//确定内容
const lastIndex = totalStrNum - totalTextLen;
let content = (totalTextLen > totalStrNum) ? text.slice(0, lastIndex + fix).concat('...') : text
ele.innerHTML = content;
}
核心比较逻辑在这里:用元素的 width
除以该元素环境下的 fontSize
,得到单行字数,操作的还是str#length
:
// 计算:单行字数、多行字数
const strNum = Math.floor(lineWidth / fontSize);
const totalStrNum = Math.floor(strNum * rows);
因为还是有偏差,我在函数提供了一个fix
参数,值是数字,来调整最终展示的字数长度。
这个方法没有兼容性的烦恼,但是在使用的时候有两个前提:
- 必须拿到包裹文本的DOM元素上的
width
、fontSize
属性; - 当这个元素的
width
发生变化(比如100%宽的元素在浏览器窗口大小被改变时候),必须去重新调用计算方法才能响应式;
css方法
处理单行
不必犹豫的text-overflow
:
.text-truncation--single-line {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
简单、兼容性好、响应式截断,相对于JavaScript方法处理单行的优势是:省略号位置完美无缺!
使用时候的前提:作用的元素需要元素有框定的width
值(特别是使用了span
包裹文本)
处理多行
首推还是 line-clamp
。
比如两行文本截断:
.text-truncation--two-lines {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
如果使用了css预处理器,比如less,还可以提取到mixin:
.text-truncation--multi-lines(@input) {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: @input;
-webkit-box-orient: vertical;
}
使用它!三行文本溢出:
.text-truncation--three-lines {
.text-truncation--multi-lines(3);
}
使用时候前提还是:作用的元素要有框定的width
值。
这方法最大的问题是兼容性:line-clamp。现在普遍支持还可以,但是如果是需要兼容ie10、ie11的朋友就要绕道了。
再来看看第二种方法,有点黑科技。
demo-jsffidle-multi-lines
思想是用到了::before
和::after
伪类,::before
内容是 "..." 的后缀,::after
是背景色的遮罩(这里用了白色)。
如果文本没有溢出,::after
和::before
在同一个位置,遮罩会把 "..." 后缀给挡住;
如果文本溢出,::before
出现在截断文本末尾,有 "..." 后缀的效果,而::after
会落在被overflow:hidden
的区域,遮罩也就隐藏掉了。
很巧妙,但是在视觉效果上,可能需要微调。
如果没有兼容性的烦恼,还是用前面第一种方法;如果确实需要兼容ie10、ie11,还是推荐这个大于JavaScrip处理多行的方法。
获取添加...后缀的信息
如果只要求 "..."后缀 视觉效果的,到上面 就结束了。
但有时候我们可以再往前走走。
比如需要鼠标悬浮在截断处理后的文本时,出现一个展示详情的tooltip,或者是溢出的文本在鼠标悬浮时,变成一个链接样式,点击打开弹窗展示详情。
这里的关键点在于,获取文本有没有溢出这个信息。
JavaScript处理文本溢出时,无论是单行还是多行,这个信息本身就是在JavaScript中,是容易获取的。如果是CSS方法处理,要想获得这个信息,得使用一些黑科技了。
这里以CSS方法处理单行文本溢出为例,CSS方法处理多行文本溢出可以类推。
css方法处理单行文本,文本是否溢出
方法一:比较目标元素的 scrollWidth
和 offsetWidth
方法很简单:
function isEllipsisActive(e) {
return (e.offsetWidth < e.scrollWidth);
}
这里科普一下offsetWidth
、clientWidth
、scrollWidth
这几个值。
看图说话:
offsetWidth
和offsetHeight
构成的box,包括 content、padding、border和滚动条;- 如果元素是
display:block;box-sizing:content
(且不含滚动条),那么offsetWidth
值能计算,等于width
+padding
(左右)+border
(左右);
- 如果元素是
clientWidth
和clientHeight
构成的box,包括 content 和 padding;scrollWidth
和scrollHeight
构成的box,包括包括当前隐藏在滚动区域之外的部分
![bd83cdca38b29943b9067865de1950f5.png](https://img-blog.csdnimg.cn/img_convert/bd83cdca38b29943b9067865de1950f5.png)
回过头来看比较的函数就容易了:
如果内容过长,scrollWidth
会包含滚动区域外的部分,也就比offsetWidth
长,也就需要添加...后缀。
方法二:拷贝比较
大致思路:
- 拷贝一份目标元素(一般通过事件获取,
e.target
),设置必要css属性(这一步决定比较是否公正),添加到body
元素,获得不受限制下的width
; - 再和目标元素的
width
比较,宽度一致则没有添加 "..." 后缀,拷贝元素的宽度大则表明添加了 "..." 后缀 - 记得删除拷贝的元素
这方法也挺巧妙,缺陷是得直接做DOM的操作。
demo-jsfiddle-copy
获取文本是否溢出的后续操作
这里说一说需求是,需要有展示详情的 tooltip。
一般处理的话,是直接给每个文本包一层 tooltip ,用上一节获取到的是否文本溢出的信息 ,去控制tooltip的隐藏与否即可(disabled属性);
如果你想挑战一下,在一个页面仅维护一个 tooltip ,当鼠标移到目标文本时,手动更新 tooltip 的位置和内容。可以参考这一篇:Plain JavaScript tooltip。
抽取复用
考虑的是Vue项目下的复用形式
JavaScript处理单行文本
可以简单抽取一个过滤器,为方便使用在全局注册:
Vue.filter('textTruncation', (val, len = 25) => {
const suffix = val.length <= len ? '' : '...'
return `${val.substring(0, len)}${suffix}`
})
css处理文本溢出
TextOverflow组件,用css去控制"..."后缀出现的位置,支持处理单行和多行文本溢出。
在文本溢出情况下,鼠标悬浮,tooltip 显示完整内容。(使用了tooltip)
<template>
<el-tooltip v-bind="$attrs" :disabled="!ellipsisActive">
<div slot="content">
<slot>{{content}}</slot>
</div>
<section :style="styleObject" @mouseenter="handleMouseEnter">{{ content }}</section>
</el-tooltip>
</template>
<script>
export default {
name: 'TyTextOverflow',
inheritAttrs: false,
props: {
content: {
type: String,
default: ''
},
rows: {
type: Number,
default: 1
}
},
data() {
return {
ellipsisActive: false,
singleLineStyle: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
},
multiLineStyle: {
overflow: 'hidden',
display: '-webkit-box',
'-webkit-box-orient': 'vertical'
}
}
},
computed: {
isMulti() {
return this.rows === 1 ? false : true
},
styleObject() {
return this.isMulti
? Object.assign({ '-webkit-line-clamp': this.rows }, this.multiLineStyle)
: Object.assign({}, this.singleLineStyle)
}
},
methods: {
handleMouseEnter(e) {
const target = e.currentTarget
this.ellipsisActive = this.isEllipsisActive(target, this.isMulti)
},
isEllipsisActive(target, isMulti) {
return isMulti
? target.offsetHeight < target.scrollHeight
: target.offsetWidth < target.scrollWidth
}
}
}
</script>
参数说明:
- content:文本内容,String 类型
- rows:控制文本行数,Number 类型
其他参数和 tooltip 保持一致。
参考链接
- stackoverflow-Understanding offsetWidth, clientWidth, scrollWidth and -Height, respectively
- stackoverflow-HTML text-overflow ellipsis detection
- stackoverflow-Plain JavaScript tooltip
- 掘金-可能是最全的“文本溢出截断省略”方案集合