文本超出一定长度之后就显示省略符号,是很常见的一个功能和业务需求。省略主要有两种方式,一种是只显示一行,另一种是指定可显示的行数,超出可显示行数的内容就省略,所以现在就整理一下不同要求下的实现方式。
单行文本
常见的处理方法是使用 CSS 的 text-overflow 属性。同样的,还需要组合 white-space 和 overflow 属性以保证效果。
<div class="limitedSpace">
{'A design language for background applications, is refined by Ant UED Team.'.repeat(20,)}
</div>
.limitedSpace{
width:200px; /* 设置父容器宽度 */
overflow:hidden; /* 隐藏溢出内容 */
white-space: nowrap; /* 强制文本不换行 */
text-overflow: clip; /* 使用省略号表示溢出文本 */
}
其中,text-overflow 用于处理当文本溢出它的容器时如何显示的情况,它的几种常见值:
clip当文本溢出容器时,任何溢出的部分将被简单地剪裁掉,也就是什么都不显示。ellipsis当文本溢出容器时,使用省略号 (...) 表示被剪裁的文本部分。
效果图👇
重要!!!
text-overflow 常常需要组合使用 white-space 和 overflow 属性才能生效。
多行文本
方法一:基于CSS
CSS 提供了一个-webkit-line-clamp属性,可以结合其他CSS属性(如display 和 overflow)来实现多行文本的省略。使用 -webkit-line-clamp 时,需要结合 display: -webkit-box 和 -webkit-box-orient
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多行文本省略</title>
<style>
.multiline-ellipsis {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
-webkit-line-clamp: 3; /* 显示的行数 */
white-space: normal;
}
</style>
</head>
<body>
<div class="multiline-ellipsis">
这是一个示例文本,它将被限制在三行。如果文本超出了三行,则会显示省略号。此特性在需要处理多行文本的布局时非常有用。这是一个示例文本,它将被限制在三行。如果文本超出了三行,则会显示省略号。此特性在需要处理多行文本的布局时非常有用。这是一个示例文本,它将被限制在三行。如果文本超出了三行,则会显示省略号。此特性在需要处理多行文本的布局时非常有用。
</div>
</body>
</html>
效果图👇
方法二:基于js的动态计算
使用css实现省略的方式比较简单,但是不够灵活,也可以基于js来实现。主要思想是计算文本所使用的实际高度是否超过文本容器的高度,如果超过了就省略。样例代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多行文本省略</title>
<style>
.container {
width: 300px;
height: 60px; /* 限制高度 */
overflow: hidden;
position: relative;
}
.text {
font-size: 16px;
line-height: 1.5em;
}
</style>
</head>
<body>
<div class="container">
<div class="text" id="text">
这是一个示例文本,它将被限制在一个特定的高度内。如果文本超出了这个高度,则会显示省略号。此特性在需要处理多行文本的布局时非常有用。为了使效果更明显,我们添加更长的文本内容来测试。
</div>
</div>
<script>
function addEllipsis() {
const container = document.querySelector('.container');
const textElement = document.querySelector('.text');
const lineHeight = parseInt(window.getComputedStyle(textElement).lineHeight);
const maxLines = Math.floor(container.clientHeight / lineHeight);
const maxHeight = maxLines * lineHeight;
if (textElement.scrollHeight > maxHeight) {
let text = textElement.innerText;
while (textElement.scrollHeight > maxHeight && text.length > 0) {
text = text.substring(0, text.length - 1);
textElement.innerText = text + '...';
}
}
}
window.addEventListener('DOMContentLoaded', (event) => {
addEllipsis();
});
</script>
</body>
</html>
代码包含三部分:
-
样式部分:
container设置了固定宽度和高度,并使用overflow: hidden来隐藏溢出的内容。
-
JavaScript 部分:
- 通过
addEllipsis函数获取text元素的实际高度,并计算出行高和最大行数。 - 不断减少
text的内容,直到其高度不再超过container的高度,并在文本末尾添加省略号。 - 由于文末的省略号是手动添加的,因此可以自定义省略方式。
- 通过
-
事件监听器:
- 通过
DOMContentLoaded事件监听器,在文档加载完成后执行addEllipsis函数。
- 通过
自定义省略展开、收起组件
现在大多数的项目是基于react框架开发的,因此实现了一个初步的省略组件,该组件可以自定义展开收起的文案。
其主要思想是:先创建一个不展示出来的虚拟元素,并将原始文本作为其innerText,计算该虚拟元素所占的行数是否超过最大行数,如果超过,则依次减少其字数,直到不超过最大行数,得到的新文本作为目标元素的innerText,最后将虚拟元素的innerText设为空。
效果图👇
// CusEllipse.tsx
import React, { useState, useRef, useEffect } from 'react';
import './App.css'
interface Props {
maxLine: number;
content: string;
className?: string;
expandText?: string; // 展开文案
collapseText?: string; // 收起文案
showEllipsis?: boolean;
}
export function E(props: Props) {
const { maxLine, content, expandText = '展开', collapseText = '收起', showEllipsis = true } = props;
const [expanded, setExpanded] = useState(false);
const contentRef = useRef<HTMLDivElement>(null);
const virtualContentRef = useRef<HTMLDivElement>(null);
const [showEllipse, setShowEllipse] = useState(showEllipsis);
const [shownContent, setShownContent] = useState(content);
const [cutIndex, setCutIndex] = useState(0);
useEffect(() => {
const cutIndex = getShownText(content, maxLine) as number; // 计算每个文本应该截断的位置
setCutIndex(cutIndex);
}, [content]);
useEffect(() => {
if (expanded) {
setShownContent(content);
} else {
const ele = contentRef.current;
if (ele) {
setShownContent(content.slice(0, Number(cutIndex)));
}
}
}, [expanded, content, maxLine, cutIndex])
const getLength = (rects) => {
var line = 0, lastBottom = 0;
for (let i = 0, len = rects.length; i < len; i++) {
if (rects[i].bottom == lastBottom) {
continue;
}
lastBottom = rects[i].bottom;
line++;
}
return line;
};
const getShownText = (content, lineClamp: number) => {
if (virtualContentRef.current === null || contentRef.current === null) return;
const span1Rects = virtualContentRef.current.getClientRects(); // 获取元素的初始完整区域
let h = getLength(span1Rects); //行数
let shownContent = content;
let cutIndex = content.length - 1;
console.log(111, span1Rects)
if (h <= lineClamp) {
setShowEllipse(false);
}
// 预设函数
while (h > lineClamp) {
shownContent = shownContent.slice(0, -1);
virtualContentRef.current.innerText = shownContent + `...${expandText}`; // 不变更state 避免引起错误状态更新
h = getLength(virtualContentRef.current.getClientRects());
cutIndex--;
}
virtualContentRef.current.innerHTML = ''
return cutIndex;
};
return (
<>
<span ref={virtualContentRef} className='hiddenSpan' >{content}</span>
<span ref={contentRef} className='ellipse'>
{shownContent}
{showEllipse && !expanded && <span>...</span>}
{showEllipse && <span className='expandedBtn' onClick={() => setExpanded(!expanded)} > {expanded ? collapseText : expandText}</span>}
</span>
</>
);
}
// index.less
.expandedBtn {
color: #1667ff;
cursor: pointer;
white-space: nowrap;
}
.expandTextBtn{
color: #2a3aff;
}
// App.tsx
import { CusEllipse } from './CusEllipse';
import './App.css'
function App() {
return (
<>
<div className='card'>
<E
maxLine={3}
content={'ddas dasdasd sadas'.repeat(10)}
/>
</div>
</>
)
}
export default App