背景
在移动端 UI 开发中,我们经常遇到这样的设计需求:
- 标题区域限制最多显示 1~2 行
- 超出部分直接截断,不显示省略号(
...) - 首行可能需要缩进,给图标/标签腾出位置
CSS 原生的 text-overflow: ellipsis 只能处理单行省略,多行省略依赖 -webkit-line-clamp,但两者都会强制显示省略号。如果设计上不要省略号,就需要一些技巧。
本文介绍一种基于"外层透明 + 内层着色"的方案,简洁优雅,兼容性好。
核心原理
┌─────────────────────────────┐
│ .textBox (color: transparent) │ ← 外层:负责截断,文字透明
│ ┌─────────────────────────┐│
│ │ <span> (color: #111) ││ ← 内层:负责显示真实颜色
│ │ 这是一段很长的文本内容... ││
│ └─────────────────────────┘│
└─────────────────────────────┘
- 外层容器设置
-webkit-line-clamp限制行数,overflow: hidden截断溢出 - 外层
text-overflow: clip(直接裁切,不产生省略号) - 外层
color: transparent,让截断处的"半截文字"不可见 - 内层
<span>设置真实颜色color: #111,只有完整可见的文字才会显示
截断发生时,被裁切的那个字符因为外层 color: transparent 而不可见,视觉上就是干净的截断,没有省略号,也没有半截字。
实现代码
HTML 结构
<!-- 单行截断 -->
<div class="textBox line1">
<span class="textContent">这是一段单行文本,超出宽度会被截断</span>
</div>
<!-- 双行截断 -->
<div class="textBox line2">
<span class="textContent">这是一段双行文本,超出两行高度会被截断,不会显示省略号</span>
</div>
<!-- 双行 + 首行缩进(给图标腾位置) -->
<div class="titleWrap">
<img class="titleIcon" src="tag.png" />
<div class="textBox line2 indent">
<span class="textContent">带图标的标题文本,首行缩进给图标腾出空间</span>
</div>
</div>
CSS 样式
/* 外层容器:负责截断 */
.textBox {
width: 200px;
overflow: hidden;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
text-overflow: clip; /* 不显示省略号 */
color: transparent; /* 文字透明,隐藏截断处的半截字 */
font-size: 16px;
line-height: 24px;
}
/* 单行截断 */
.line1 {
-webkit-line-clamp: 1;
max-height: 24px; /* line-height * 1 */
}
/* 双行截断 */
.line2 {
-webkit-line-clamp: 2;
max-height: 48px; /* line-height * 2 */
}
/* 首行缩进(给绝对定位的图标腾位置) */
.indent {
text-indent: 20px;
}
/* 内层 span:负责显示真实颜色 */
.textContent {
color: #111111;
}
React + CSS Modules 写法
// index.tsx
import { View } from '@tarojs/components';
import Style from './index.module.less';
function TruncateText({ text, lines = 2, indent = false }): JSX.Element {
return (
<View className={`${Style.textBox} ${Style[`line${lines}`]} ${indent ? Style.indent : ''}`}>
<span className={Style.textContent}>{text}</span>
</View>
);
}
// index.module.less
.textBox {
width: 202px;
overflow: hidden;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
text-overflow: clip;
color: transparent;
font-size: 16px;
font-family: PingFang SC;
font-weight: 600;
line-height: 24px;
}
.line1 {
-webkit-line-clamp: 1;
max-height: 24px;
}
.line2 {
-webkit-line-clamp: 2;
max-height: 48px;
}
.indent {
text-indent: 20px;
}
.textContent {
color: #111111;
}
对比:为什么不用常规方案
方案一:text-overflow: ellipsis(单行)
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
问题:只支持单行,且必须显示省略号。
方案二:-webkit-line-clamp + ellipsis(多行)
.multiEllipsis {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
问题:会在截断处显示 ...,无法隐藏。当配合 text-indent 使用时,省略号位置可能不符合预期。
方案三:本文方案(透明 + 着色)
.textBox { color: transparent; text-overflow: clip; -webkit-line-clamp: 2; }
.textContent { color: #111; }
优势:
- 不显示省略号,截断干净
- 支持单行和多行,只需改
-webkit-line-clamp的值 - 兼容
text-indent首行缩进场景 - 不需要 JS 计算,纯 CSS 实现
- 兼容性好(iOS Safari、Android Chrome、微信小程序 WebView 均支持)
进阶:配合图标的首行缩进
实际业务中常见的场景是标题前面有一个标签图标,需要首行缩进:
┌──────────────────────────┐
│ [热] 这是一个很长的标题文 │ ← 首行缩进,图标绝对定位
│ 本内容超出两行会被截断 │ ← 第二行正常对齐
└──────────────────────────┘
<View className={Style.titleWrap}>
<ImageCache className={Style.titleIcon} src={tagUrl} />
<View className={`${Style.textBox} ${Style.line2} ${Style.indent}`}>
<span className={Style.textContent}>{title}</span>
</View>
</View>
.titleWrap {
position: relative;
}
.titleIcon {
position: absolute;
left: 0;
top: 3.5px;
width: 16px;
height: 16px;
}
.indent {
text-indent: 20px; // 图标宽度 16px + 间距 4px
}
text-indent 只作用于首行,第二行自动左对齐,完美配合绝对定位的图标。
注意事项
color: transparent会让外层所有文字透明,必须确保内层<span>覆盖了全部文本内容-webkit-line-clamp是 WebKit 私有属性,但在移动端(iOS Safari、Android Chrome、小程序 WebView)兼容性很好- 建议配合
max-height做双重保险,防止极端情况下line-clamp失效导致容器撑开 word-break: break-all确保长英文/数字也能正确换行截断