CSS实现多行文本展开与收起技巧

发表时间: 2024-06-12 11:20

背景

在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:

那么,如何通过纯css实现这样的效果呢?

实现的难点

(1)位于多行文本右下角的 展开收起按钮

(2)展开和收起两种状态的切换。

(3)文本不超过指定行数时不展示 展开收起按钮

一、位于右下角的“展开收起”按钮

多行文本截断

假设有这样一个结构:

<div className="text">    银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,</div>

多行文本超出省略,主要用到用到 line-clamp,关键样式如下:

.text {    display:-webkit-box;    -webkit-line-clamp:2;    -webkit-box-orient:vertical;    overflow:hidden;}

右下角环绕效果

下面放一个按钮,然后设置浮动,可以使文本环绕按钮,在通过一个 margin-top 可以使按钮移动到右下角

<div className="text">    <button className="btn">展开</button>    银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银,银泰百货,银,银泰百货,银,银泰百货,银,银泰百货,银,银泰百货,银,银泰百货,银</div><style>.btn{    float:right;    margin-top:50px;}</style>

可以看到,虽然按钮到了右下角,但是文本却没有环绕按钮上方的空间,空出了一大截

此时我们可以通过多个浮动元素进行调整,这里用伪元素 ::before 来代替

<style>.text:before{    content:'';    float:right;    width:10px;    height:58px;/*先随便设置一个高度*/    background:red}</style>

通过清除浮动使伪元素位于按钮的上方

<style>.btn{    float:right;    clear:both;}</style>

可以看到,现在文本是完全环绕在右侧的两个浮动元素了,只要把红色背景的伪元素宽度设置为0(或者不设置宽度,默认就是 0),就实现了右下角环绕的效果

<style>.text:before{    content:'';    float:right;    width:0;/*设置为0,或者不设置宽度*/    height: 50px;/*先随便设置一个高度*/}</style>

动态高度

上面虽然完成了右下加环绕,但是高度是固定的,如何动态设置呢?

这里可以用到 calc 计算,用整个容器高度减去按钮的高度即可,如下:

<style>.text:before{    content:'';    float:right;    width:0;/*设置为0,或者不设置宽度*/    height: calc(100% - 24px);/*先随便设置一个高度*/}</style>

很可惜,好像并没有什么效果,打开控制台看看,结果发现 calc(100% - 24px) 计算高度为 0

原因就是高度 100% 失效的问题,关于这类问题网上的分析有很多

通常的解决方式是给父级指定一个高度,但是这里的高度是动态变化的,而且还有展开状态,高度更是不可预知,所以设置高度不可取。

除此之外,其实还有另一种方式,那就是利用 flex 布局

因此,这里需要给 .text 包裹一层,然后设置 display: flex

<div className="wrap">    <div className="text">        <button className="btn">展开</button>        银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银,银泰百货,银,银泰百货,银,银泰百货,银,银泰百货,银,银泰百货,银,银泰百货,银    </div></div><style>.wrap{    display :flex;}</style>

这样下来,刚才的计算高度就生效了,改变文本的行数,同样位于右下角

二、“展开”和“收起”两种状态

CSS 状态切换,需要用到 input type="checkbox"

我们首先加一个 input,然后把之前的 button 换成 label ,并且通过 for 属性关联起来。

注意!在 jsx 语法中 for 属性要写成 htmlFor

<div className="wrap">    <input type="checkbox" id="exp">    <div className="text">        <label className="btn" htmlFor="exp">展开</label>        银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银泰百货,银,银泰百货,银,银泰百货,银,银泰百货,银,银泰百货,银,银泰百货,银,银泰百货,银    </div></div>

这样,在点击 label 的时候,实际上是点击了 input 元素

现在来添加两种状态,分别是只显示 2 行和不做行数限制

<style>.exp:checked+.text{    -webkit-line-clamp: 999; /* 设置一个足够大的行数即可 */}</style>

兼容版本可以直接设置最大高度 max-height 为一个较大的值,或者直接设置为 none

<style>.exp:checked+.text{    max-height:none;}</style>

展开 按钮在点击后应该变成 收起 ,使用 伪类 content 生成技术

具体做法就是去除或者隐藏按钮里面的文字,采用伪元素生成

<label class="btn" for="exp"></label> <!-- 去除按钮文字 --><style>.btn::after{    content:"展开"; /* 采用content 生成 */}</style>

添加 .checked 状态

<style>.exp:checked+.text .btn::after{    content:"收起";}</style>

三、文本行数的判断

当文本较少时,此时是没有发生截断,需要隐藏展开按钮,CSS 是没有这类逻辑判断

大多数我们都需要从别的角度,采用 “障眼法” 来实现

在文本末尾添加一个元素,为了不影响原有布局,这里设置了绝对定位,这里用 .text::after 伪元素来代替

把刚才这个元素设置一个足够大的尺寸,比如 100% * 100%

<style>.text::after{    content: ' ';    width:100%;    height:100%;    position: absolute;    background: #fff;}</style>

那么如果此时文本没有截断,这个元素会把按钮覆盖掉,如果有截断,这个元素会往下移,就不会覆盖按钮

点击展开后按钮丢失,无法收起

此时,我们可以添加一下:checked状态即可,在展开时隐藏覆盖层

<style>.exp:checked+.text::after{    visibility:hidden;}</style>

这样,就实现了在文字较少的情况下隐藏展开按钮的功能