前言
业务上经常会遇到 文本内容超出容器区域进行省略打点展示 的需求,而展示省略也存在多种场景和形式,今天我们来聊一聊这些细节。
- 单行文本超长打点省略(CSS)
- 多行文本超长打点省略(CSS)
- 单行/多行文本超长打点省略(JS)
- 单行/多行文本超长在 中间 位置打点省略(JS)
- 超长元素块整块打点省略(CSS)
- 自定义打点省略标签(JS)
一、单行文本超长打点省略(CSS)
CSS 属性支持对单行文本超长时在 尾部 进行打点省略。为文本容器设置以下属性:
css
.text-container{
/* 明确指定容器的宽度 */
width: 200px;
/* 强制将文字排列在一行,不进行换行 */
white-space: nowrap;
/* 超出隐藏 */
overflow: hidden;
/* 在文本溢出时,显示省略符号来代表被修剪的文本 */
text-overflow: ellipsis;
}
二、多行文本超长打点省略(CSS)
对于多行文本的超长省略,可使用 -webkit-line-clamp
来指定显示文本的行数,比如下面 两行文本超出进行打点省略。
css
.text-container{
/* 明确指定容器的宽度 */
width: 200px;
/* 超出隐藏 */
overflow: hidden;
/* 在文本溢出时,显示省略符号来代表被修剪的文本 */
text-overflow: ellipsis;
/* 限制文本展示的行数 */
-webkit-line-clamp: 2;
/* 结合 -webkit-line-clamp 使用,将对象作为弹性伸缩盒子模型显示 */
display: -webkit-box;
/* 设置伸缩盒对象的子元素的排列方式 */
-webkit-box-orient: vertical;
}
但它的短板在于 兼容性一般 ,-webkit-line-clamp
属性只有 WebKit
内核的浏览器才支持,多适用于移动端页面(移动设备浏览器更多是基于 WebKit 内核)。
三、单行/多行文本超长打点省略(JS)
选用 JS 来实现更多的是要解决 CSS 多行文本超出省略 的兼容问题,这里的实现同样适用于 单行文本超出省略。
其原理是根据文本字符的长度,根据容器 font-size
大小来计算能够容纳多少文本字符,若无法全部容纳则对文本进行截断展示。
html
<style>
.text-container{
width: 200px;
}
</style>
<div class="text-container"></div>
<script>
// const text = 'this is a long text. this is a long text. this is a long text. this is a long text. this is a long text. this is a long text. this is a long text. ';
const text = '这是一段超长的文本。这是一段超长的文本。这是一段超长的文本。这是一段超长的文本。这是一段超长的文本。这是一段超长的文本。';
const lineNum = 2;
const container = document.querySelector('.text-container');
let { width, fontSize } = window.getComputedStyle(container);
width = +width.slice(0, -2); // 拿到的值是 string 带 px,这里转成 number
fontSize = +fontSize.slice(0, -2);
// 1. 按中英文计算文本字符长度(中文两个字符,英文)
const textLength = computedTextLength(text);
// 2. 计算容器一行所能容纳的字符数
const lineCharNum = Math.floor(width / fontSize) * 2;
// 多行可容纳总字数
const totalStrNum = Math.floor(lineCharNum * lineNum);
// 内容截取
let content = '';
if (textLength > totalStrNum) {
const lastIndex = totalStrNum - textLength;
content = sliceTextLength(text, totalStrNum - 3).concat('...'); // ... 代表三个字符
} else {
content = text;
}
container.innerHTML = content;
</script>
computedTextLength
和 sliceTextLength
会将 中文 按照两个字符计算。
js
const computedTextLength = text => {
let length = 0;
for (let i = 0; i < text.length; i ++) {
if (text.charCodeAt(i) < 0 || text.charCodeAt(i) > 255) {
length += 2;
} else {
length += 1;
}
}
return length;
}
const sliceTextLength = (text, sliceLength) => {
let length = 0, newText = '';
for (let i = 0; i < text.length; i ++) {
if (text.charCodeAt(i) < 0 || text.charCodeAt(i) > 255) {
length += 2;
} else {
length += 1;
}
if (length <= sliceLength) {
newText += text[i];
} else {
break;
}
}
return newText;
}
同样,JS 实现也存在短板:省略号展示的位置存在计算偏差(一个 font-size 的宽度对应两个字符存在一定偏差(字母 和 空格)),并不会像 CSS 能够将省略展示位置刚刚好。
四、单行/多行文本超长在 中间 位置打点省略(JS)
这类需求常见的场景是对 超长的文件名称进行中间位置省略 ,这样可以展示出文件后缀类型。如:这是一个很长很长...很长很长的文件.pdf。
CSS 对超长文本省略的打点位置只能在末尾,采用 JS 实现可以控制省略打点位置,我们改造一下上面 「单行/多行文本超长打点省略(JS)」 截取逻辑。
js
if (textLength > totalStrNum) {
// const lastIndex = totalStrNum - textLength;
// content = sliceTextLength(text, totalStrNum - 3).concat('...'); // ... 代表三个字符
const middleIndex = totalStrNum / 2;
content = `${sliceTextLength(text, middleIndex)}...${lastSliceTextLength(text, middleIndex - 3)}`;
}
const lastSliceTextLength = (text, sliceLength) => {
let length = 0, newText = '';
for (let i = text.length - 1; i > 0; i --) {
if (text.charCodeAt(i) < 0 || text.charCodeAt(i) > 255) {
length += 2;
} else {
length += 1;
}
if (length <= sliceLength) {
newText = text[i] + newText;
} else {
break;
}
}
return newText;
}
五、超长元素块整块打点省略(CSS)
上面涉及的场景都是容器内渲染纯文本进行省略展示,假如容器内是行内块元素(业务上的数据标签),如何实现一行排不下时整个标签被移除进行打点省略展示呢(非 标签展示了一部分被截取)?
容器内 DOM 结构如下:
html
<div class="container">
<span class="tag">前端</span>
<span class="tag">后端</span>
<span class="tag">测试</span>
<span class="tag">UI</span>
<span class="tag">产品</span>
</div>
要实现省略需要两步操作:
第一步,为容器设置内容超出打点省略:
css
.container{
width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
第二步,将 span 元素设置为 display: inline-block
,这样才能实现标签在排不下时,将标签整块都省略,而不是让标签只显示一部分打点省略。
css
.tag{
display: inline-block;
}
效果图如下:
但是经过测试会发现 iOS 及 safari 浏览器下标签并未按照整块进行省略,解决办法是使用 多行省略替代单行省略。
css
.container{
width: 200px;
/* white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis; */
/* 注意这里使用 normal */
white-space: normal;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
六、自定义打点省略标签(JS)
现在需求进行了升级,期望省略的 ... 能够有操作:移入或点击 能够查看所有 tag。类似下方示意图,需要我们将 打点省略 作为一个元素去实现。
这种情况我们需要采用 JS 来实现。首先渲染节点和样式,
html
<style>
*{
padding: 0;
margin: 0;
}
.container{
margin: 100px auto;
width: 180px;
/* 容器要指定高度 */
height: 28px;
/* 容器要设置相对定位 */
position: relative;
}
.tag{
display: inline-block;
padding: 2px 4px;
border-radius: 2px;
background: purple;
color: #fff;
margin-right: 4px;
}
.tag:last-child{
margin-right: 0;
}
</style>
<div class="container"></div>
<script>
const createTagEl = text => {
const span = document.createElement('span');
span.className = "tag";
span.innerHTML = text;
return span;
}
const tags = ['前端', '后端', '测试', 'UI', '产品'];
const container = document.querySelector('.container');
tags.forEach(tag => container.appendChild(createTagEl(tag)));
</script>
然后根据标签内容动态计算是否需要添加 打点省略 元素(可参考代码注释)。
js
...
const ellipsisWidth = 22 + 4; // 假设省略打点元素的宽度为 22,左边距为 4
const { offsetWidth, clientHeight, scrollHeight, children } = container; // 容器要指定宽高
// 需要打点省略
if (scrollHeight > clientHeight) {
// 找到第一个被换行的 tag 索引(基于距离父元素的 偏移量 来计算)
let sliceEndIndex = Array.from(children).findIndex(child => child.offsetTop !== 0);
// 如果第一行排不下 ellipsis tag,调整 sliceEndIndex 索引
while (sliceEndIndex > 0) {
const child = children[sliceEndIndex - 1];
// 剩余的空间可以用于展示 ellipsis tag
if (offsetWidth - (child.offsetWidth + child.offsetLeft) >= ellipsisWidth) {
break;
}
sliceEndIndex --; // 要删除标签
}
// 删除排不下的 tag
let length = children.length - sliceEndIndex;
while (length > 0) {
container.removeChild(children[children.length - 1]);
length --;
}
// 创建 ellipsis tag
container.appendChild(createTagEl('...'));
}