背景

在移动端 UI 开发中,我们经常遇到这样的设计需求:

  • 标题区域限制最多显示 1~2 行
  • 超出部分直接截断,不显示省略号(...
  • 首行可能需要缩进,给图标/标签腾出位置

CSS 原生的 text-overflow: ellipsis 只能处理单行省略,多行省略依赖 -webkit-line-clamp,但两者都会强制显示省略号。如果设计上不要省略号,就需要一些技巧。

本文介绍一种基于"外层透明 + 内层着色"的方案,简洁优雅,兼容性好。

核心原理

┌─────────────────────────────┐
│  .textBox (color: transparent)  │  ← 外层:负责截断,文字透明
│  ┌─────────────────────────┐│
│  │ <span> (color: #111)    ││  ← 内层:负责显示真实颜色
│  │ 这是一段很长的文本内容... ││
│  └─────────────────────────┘│
└─────────────────────────────┘
  1. 外层容器设置 -webkit-line-clamp 限制行数,overflow: hidden 截断溢出
  2. 外层 text-overflow: clip(直接裁切,不产生省略号)
  3. 外层 color: transparent,让截断处的"半截文字"不可见
  4. 内层 <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 只作用于首行,第二行自动左对齐,完美配合绝对定位的图标。

注意事项

  1. color: transparent 会让外层所有文字透明,必须确保内层 <span> 覆盖了全部文本内容
  2. -webkit-line-clamp 是 WebKit 私有属性,但在移动端(iOS Safari、Android Chrome、小程序 WebView)兼容性很好
  3. 建议配合 max-height 做双重保险,防止极端情况下 line-clamp 失效导致容器撑开
  4. word-break: break-all 确保长英文/数字也能正确换行截断