多行文本溢出的一个解决方案

前端开发
2024年02月23日
2190

有下面这样的一个 HTML 元素:

html
<div class="content"> 插件一般是可独立完成某个或一系列功能的模块。 一个插件是否引入一定不会影响到系统本身的正常运行(除非它和另一个插件存在依赖关系)。 插件在何时被引入,何时被调用都是由系统来调度的。 一个系统可以存在多个插件,这些插件可以通过系统预定的方式进行组合。 </div>

我们给他加上一点样式:

css
.content { width: 300px; background-color: lightblue; }

页面上可以看到 400px 宽度的亮蓝色矩形:

image-20240222164110777.png

通常情况下,我们都不会一下子就展示这么多数据,一般情况都不会超过 3 行(我乱说的)。

而单行文本溢出已经有了很成熟的解决方案了。

css
.ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

image-20240222164726326.png

多行文本溢出

这里就不讨论使用 JavaScript 对内容截取的方案了,我们看看纯 css 方案如何解决多行文本溢出。

-webkit-line-clamp

-webkit-line-clamp 可以把块容器中的内容限制为指定的行数。

它只有在 display 属性被设置为 -webkit-box 或者 -webkit-inline-box 并且 box-orient 属性设置成 vertical 时才有效果。

这个属性最初是在 Webkit 中实现的,但存在一些问题。由于需要支持旧版本的浏览器, CSS Overflow Module Level 4 中被标准化。

css
.webkit-line-clamp { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }

看看效果:

image-20240222165833018.png

可以说是非常完美了,但这个方案需要考虑一下兼容性。

另一种方案

在写样式之前,需要把 DOM 结构做一下修改,给内容多加一层嵌套:

html
<div class="content float-ellipsis"> <div class="inner"> 插件一般是可独立完成某个或一系列功能的模块。 一个插件是否引入一定不会影响到系统本身的正常运行(除非它和另一个插件存在依赖关系)。 插件在何时被引入,何时被调用都是由系统来调度的。 一个系统可以存在多个插件,这些插件可以通过系统预定的方式进行组合。 </div> </div>

然后加入以下的样式片段:

css
.float-ellipsis { height: 60px; line-height: 20px; overflow: hidden; } .float-ellipsis::before { content: ''; float: left; width: 40px; height: 60px; } .float-ellipsis::after { content: '...'; float: right; position: relative; left: 100%; width: 40px; height: 20px; text-align: right; background-image: linear-gradient(to right, transparent, lightblue); transform: translate(-100%, -100%); } .float-ellipsis .inner { float: right; width: 100%; margin-left: -40px; word-break: break-all; }

效果与 -webkit-line-clamp 差不多,甚至省略号还多了个渐变的效果:

image-20240223100304899.png

从 CSS 上面,似乎我们只需要给容器加个 ::after 伪类,直接放置 ... 省略号到右下角也可以实现相应的效果。

html
<div class="content only-after"> <div class="inner"> 插件一般是可独立完成某个或一系列功能的模块。 一个插件是否引入一定不会影响到系统本身的正常运行(除非它和另一个插件存在依赖关系)。 插件在何时被引入,何时被调用都是由系统来调度的。 一个系统可以存在多个插件,这些插件可以通过系统预定的方式进行组合。 </div> </div>
css
.only-after { position: relative; margin: 20px auto; height: 60px; line-height: 20px; overflow: hidden; } .only-after::after { content: '...'; position: absolute; right: 0; bottom: 0; width: 40px; height: 20px; text-align: right; background-image: linear-gradient(to right, transparent, lightblue); }

image-20240223101125824.png

在这个案例看来,两者好像没什么区别。但是看一下另一组案例:

html
<div class="content float-ellipsis"> <div class="inner"> 插件一般是可独立完成某个或一系列功能的模块。 </div> </div> <div class="content only-after"> <div class="inner"> 插件一般是可独立完成某个或一系列功能的模块。 </div> </div>

image-20240223101215550.png

显然,仅用 ::after 在内容没有发生溢出时,界面上依然会展示 ...,这与我们预期是不符合的(仅在在文本溢出时才显示 ...)。所以我们加上了 ::before 元素,再与 .inner::after 元素结合,采用浮动布局的形式来实现这个多行文本溢出的处理。

布局的奇妙现象

float 布局

先看一下一个很普通的浮动布局(float)的例子:

html
<div class="container"> <div class="box box-1">1</div> <div class="box box-2">2</div> <div class="box box-3">3</div> </div>

如上面的 DOM 结构,如果不加任何 CSS 的话,三个 box 应该是从上往下排列放置。但如果我们想排在同一行,则需要一点 CSS 来控制了:

css
.container { width: 400px; height: 100px; border: 1px solid #333; } .box { width: 100px; height: 100px; text-align: center; line-height: 100px; } .box-1 { float: left; background-color: green; } .box-2 { float: right; width: 200px; background-color: skyblue; opacity: .5; } .box-3 { float: right; background-color: orange; }

image-20240223113817979.png

这是一个比较常见的浮动布局。需要注意的时,box-2box-3 都是右浮动 float: right; 的,在浮动布局中,放置元素会根据他的 DOM 所在的顺序依次放置,所以我们能看到的是 1 3 2 这样的盒子方式。

在设置宽度的时候,也是刚刚好给到了容器可以容纳的宽度,所以我们可以看到所有盒子都会并排在同一行。如果我们给 box-1box-2 增加了宽度的话,box-3 就会被挤到下一行重新放置:

diff
.box-2 { float: right; - width: 200px; + width: 250px; background-color: skyblue; opacity: .5; }

image-20240223114517767.png

在上面的代码中,我们给 box-2 增加了 50px 的宽度,当前行剩余宽度就剩下:400 - 100 - 250 = 50 像素的宽度,而 box-3 宽度为 100px,当前行空间不足,所以会被放置到下一行。

结合一下 margin

我们先看一下 margin 带来的效果:

image-20240223140928059.png

从上面的图示可以看出:当 margin 设置成负值时,其它元素是可以放置到该元素原本占据的空间上的(蓝色元素有一部分与橙色元素重叠了)。

基于这个原理,我们把 CSS 作一些修改:

diff
.box-2 { float: right; width: 250px; + margin-left: -50px; background-color: skyblue; opacity: .5; }

image-20240223141525954.png

可以看到,原本在另外一行的 box-3 它回到了第一行,并且部分内容与 box-2 重叠了。如果我们把 margin-left 设置成 box-3 的宽度(100px)时,那么 box-3 会完全重叠在 box-2 的空间中:

image-20240223141952180.png

接下来我们把 box-2 的宽度设置成与容器宽度一致。

css
.box-2 { float: right; width: 100%; margin-left: -100px; background-color: skyblue; opacity: .5; }

image-20240223142334100.png

貌似与我们想象中的不太一样。width: 100%; 时,box-2 应该独自占据一行才对。就像下面这种子:

image-20240223143230164.png

但偏偏 box-1box-2 在同一行是正确的,因为 margin-left: -100px 使得 box-2实际布局中占据的空间宽度应该为 100% - 100px = 300px

上面讨论的布局与位置都是基于等高度的情况,下面将高度进行一下调整:

diff
.box-2 { float: right; width: 100%; + height: 150px; margin-left: -100px; background-color: skyblue; opacity: .5; }

可能你会觉得它应该是这样子的:

image-20240223143643530.png

但实际上,box-3 会被放置在 box-1 的正下方,并与 box-2 有一半区域发生了重叠:

image-20240223143731791.png

为什么会这样子呢,我们只需要改动一下 box-1 的高度,就能更清晰地看清楚了:

css
.box-1 { float: left; height: 40px; line-height: 40px; background-color: green; }

image-20240223144024796.png

为什么会这样子呢?事实上,虽然 box-2 的宽度是 100%(400px),但是它在布局上占据的宽度空间却只有 width - marginLeft 也就是 300px。

box-1 的宽度为 100px,高度为 50px,只占据了 box-2margin 空间中的一小部分,把 box-1 放置完毕后,剩余的部分空间足够把 box-3 放置进去(甚至还有剩余)。所以就会呈现出图上面的效果。

结合 position 和 translate

在开始之前,我们先把样式恢复一下:

css
.box-1 { float: left; background-color: green; } .box-2 { float: right; width: 100%; height: 150px; margin-left: -100px; background-color: skyblue; opacity: .5; } .box-3 { float: right; background-color: orange; }

image-20240223145313170.png

我们现在想要做的是,把原本位于左下角的 box-3 挪到右上角:

diff
.box-3 { float: right; + position: relative; + left: 100%; background-color: orange; + transform: translate(-100%, -100%); }

image-20240223150428353.png

为什么不直接使用 translate 呢,因为在案例中我们的宽度位置是固定的,但实际上我们开发时宽度可能是一个未知的值,所以使用了 left: 100%,来让 box-3 定位到最后侧(它是基于容器宽度的百分比值),然后再使用 translate(-100%, -100%) 使其放置到对应的位置(相对于自身的百分比值)。

多行文本方案所使用到的 css 相关知识已经完毕,接下来我们看看动图效果来理解一下为什么要这样子做:

test.gif

box-2 的高度不超过容器高度时,box-3 会在容器之外;而当 box-2 的高度超出容器的高度时,box-3 则在容器的右侧。

以上,就是本文的所有内容了。