头图

CSS to achieve multi-line text "expand and collapse"

XboxYan
中文

Expanding and collapsing multi-line text is a very common interaction, as shown in the figure below

展开3.gif

The main difficulties in implementing this type of layout and interaction are as follows

  1. The "Expand and Collapse" button at the bottom right corner of the multi-line text
  2. Switch between "expanded" and "collapsed" states
  3. When the text does not exceed the specified number of lines, the "Expand Collapse" button is not displayed

To be honest, before looking at this layout alone, even with the help of JavaScript is not an easy task (you need to calculate the width of the text to dynamically intercept the text, vue-clamp this), let alone the following interaction and judgment logic , But after my pondering, in fact, pure CSS can also be achieved perfectly, let's take a step by step to see how to achieve it~

1. The "Expand and Collapse" button at the bottom right corner

Many design students like this design. Put the button in the lower right corner and mix instead of a single line, which may be more visually comfortable and beautiful. Let’s take a look at multi-line text truncation, this one is relatively simple

  1. Multi-line text truncation

Suppose there is such an html structure

<div class="text">
  浮动元素是如何定位的
正如我们前面提到的那样,当一个元素浮动之后,它会被移出正常的文档流,然后向左或者向右平移,一直平移直到碰到了所处的容器的边框,或者碰到另外一个浮动的元素。
</div>

Multi-line text is beyond omission. You should be familiar with this. The main use is line-clamp . The key styles are as follows

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

image.png

  1. Surround effect in the lower right corner

When it text wrapping effect , you can generally think of floating float , yes, don’t think that floating is a thing of the past, and specific scenarios are still very useful. For example, put a button below, and then set the floating

<div class="text">
  <button class="btn">展开</button>
  浮动元素是如何定位的
正如我们前面提到的那样,当一个元素浮动之后,它会被移出正常的文档流,然后向左或者向右平移,一直平移直到碰到了所处的容器的边框,或者碰到另外一个浮动的元素。
</div>
.btn {
  float: left;
  /*其他装饰样式*/
}

image.png

If set to float right

.btn {
  float: right;
  /*其他装饰样式*/
}

image.png

At this time, there is already surrounding , but it is located in the upper right corner. How to move the button to the lower right corner? First try margin

.btn {
  float: right;
  margin-top: 50px;
  /*其他装饰样式*/
}

image.png

As you can see, although the button is in the lower right corner, the text does not surround the space above the button, leaving a lot of space. Is there nothing I can do?

Although margin not solve the problem, the entire text is still affected by the floating button. What if there are multiple floating elements? Here use pseudo-element to ::before instead

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

image.png

Now that the button is to the left of the pseudo-element, how to move it below? Very simple, clear the float clear: both;

.btn {
  float: right;
  clear: both;
  /*其他装饰样式*/
}

image.png

As you can see, the text is now completely wrapped around the two floating elements on the right. As long as the width of the pseudo-element of the red background is set to 0 (or no width is set, the default is 0), the bottom right corner wrapping effect is achieved.

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

image.png

  1. Dynamic height

Although the upper right corner is added above, the height is fixed. How to set it dynamically? Here you can use calc calculate, just subtract the height of the button from the height of the entire container, as follows

.text::before{
  content: '';
  float: right;
  width: 0;
  height: calc(100% - 24px);
}

image.png

It's a pity that it doesn't seem to have any effect. I opened the console and found that calc(100% ) 160a6878dc7cb7 calculated height is 0

image.png

The reason is actually very easy to think of, that is, the height 100% fails . There are many online analysis of this type of problem. The usual solution is to assign a height to the parent, but the height here is dynamically changing, and there are also In the unfolded state, the height is even more unpredictable, so setting the height is not advisable.

In addition, there is actually another way, which is to use flex layout . The approximate method is to calculate the change height by percentage in the sub-items of flex layout css-flexbox

If the flex item has align-self: stretch, redo layout for its contents, treating this used size as its definite cross size so that percentage-sized children can be resolved.

Therefore, here you need to .text with a layer, and then set display: flex

<div class="wrap">
  <div class="text">
    <button class="btn">展开</button>
    浮动元素是如何定位的
  正如我们前面提到的那样,当一个元素浮动之后,它会被移出正常的文档流,然后向左或者向右平移,一直平移直到碰到了所处的容器的边框,或者碰到另外一个浮动的元素。
  </div>
</div>
.wrap{
  display: flex;
}
In practice, display: grid and display: -webkit-box are equally effective, with similar principles

In this way, the calculated height just now takes effect. Change the number of lines of the text, which is also located in the lower right corner~

image.png

In addition, the dynamic height can also be achieved with a negative margin (performance will be slightly better than calc)

.text::before{
  content: '';
  float: right;
  width: 0;
  /*height: calc(100% - 24px);*/
  height: 100%;
  margin-bottom: -24px;
}

At this point, the surround effect in the lower right corner is basically completed, the ellipsis is also located before the expand button, the complete code can be viewed codepen, the lower right corner multi-line expansion surround effect

4. Compatibility with other browsers

The above implementation is the most perfect way to deal with it. I thought that compatibility was not a big problem. After all, only text truncation and floating were used. -webkit-line-clamp is the -webkit- , but firefox is supported. safari and firefox all messed up!

image.png

This is a bit uncomfortable. Have all the previous efforts been wasted? It is impossible to ignore these two, otherwise it can only be a demo and cannot be used in a production environment.

Quickly open the console to see what the reason is. After searching, it was found that it was display: -webkit-box ! After setting this attribute, the original text seems to become a whole block, and the floating element cannot produce the wrapping effect. After removing it, the floating will be normal.

Kapture 2021-05-11 at 10.33.19.gif

So the question is: does not have display: -webkit-box How to achieve multi-line truncation?

In fact, the above efforts have achieved the effect of wrapping in the lower right corner. If a maximum height is set when the number of lines is known, will it also complete the multi-line truncation? In order to set the height easily, you can add a line height line-height, needs to be set to 3 lines, then the height is set to line-height * 3

.text {
  /*
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  */
  line-height: 1.5;
  max-height: 4.5em;
  overflow: hidden;
}

In order to facilitate better control of the number of rows, the number of commonly used rows can be separated by the attribute selector (usually not too many), as follows

[line-clamp="1"] {
  max-height: 1.5em;
}
[line-clamp="2"] {
  max-height: 3em;
}
[line-clamp="3"] {
  max-height: 4.5em;
}
...
<!--3行-->
<div class="text" line-clamp="3">
...
</div>
<!--5行-->
<div class="text" line-clamp="5">
 ...
</div>

image.png

You can see that it is basically normal, except that there is no ellipsis, now add the ellipsis, just before the expand button, you can use pseudo-element to achieve

.btn::before{
  content: '...';
  position: absolute;
  left: -10px;
  color: #333;
  transform: translateX(-100%)
}

image.png

In this way, Safari and Firefox is basically completed, the complete code can be viewed in codepen. The bottom right corner of the multi-line expansion surround effect (fully compatible)

Two, "expanded" and "collapsed" two states

When it comes to CSS state switching, everyone can think of input type="checkbox" . Here we also need to use this feature, first add a input , then replace the previous button label, and associate it with the for

<div class="wrap">
  <input type="checkbox" id="exp">
  <div class="text">
    <label class="btn" for="exp">展开</label>
    浮动元素是如何定位的
  正如我们前面提到的那样,当一个元素浮动之后,它会被移出正常的文档流,然后向左或者向右平移,一直平移直到碰到了所处的容器的边框,或者碰到另外一个浮动的元素。
  </div>
</div>

In this way, when you click label , you actually clicked the input add two states, which are to display only 3 rows and do not limit the number of rows.

.exp:checked+.text{
  -webkit-line-clamp: 999; /*设置一个足够大的行数就可以了*/
}

For compatible versions, you can directly set the maximum height max-height to a larger value, or directly set it to none

.exp:checked+.text{
  max-height: none;
}

展开2.gif

There is another small problem here. The "Expand" button should become "Collapse" after being clicked. How to modify it?

There is a trick. Anyone who needs to dynamically modify the content can use the pseudo-class content generation technology. The specific method is to remove or hide the text in the button and use pseudo-element generation.

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

Add : checked status

.exp:checked+.text .btn::after{
  content:'收起'
}

Compatible version because the ellipsis in front is simulated and cannot be automatically hidden, so additional processing is required

.exp:checked+.text .btn::before {
    visibility: hidden; /*在展开状态下隐藏省略号*/
}

展开3.gif

The effect is basically the same as that at the beginning of this article. The complete code can be viewed at codepen. Multi-line expand and collapse interaction ,


Compatible version can be viewed codepen multi-line expand and collapse interaction (full compatibility)

One more point, if you set a suitable value max-height is a suitable value . For the specific principle, please refer to : dynamic height transition animation , and transition animation can also be added

.text{
  transition: .3s max-height;
}
.exp:checked+.text{
  max-height: 200px; /*超出最大行高度就可以
}

Kapture 2021-05-11 at 14.21.55.gif

Three, the judgment of the number of text lines

The above interaction has basically met the requirements, but there will still be problems. For example when there is less text , at this time there is no truncation occurs, that is no ellipsis, but the "expand" button in the lower right corner, but still, how to hide it?

image.png

Usually the js solution is easy. Compare the scrollHeight and clientHeight , and then add the corresponding class name. Below is the pseudo code

if (el.scrollHeight > el.clientHeight) {
  // 文本超出了
  el.classList.add('trunk')
} 

So, how does CSS achieve this kind of judgment?

To be sure, CSS does not have this kind of logical judgment. Most of us need to adopt "obstructive method" from another perspective. For example, in this scenario, when there is no truncation it occurs, representing the text fully visible, and then, if end of the text add an element (red square), in order not to affect the original layout, here set absolute positioning

.text::after {
    content: '';
    width: 10px;
    height: 10px;
    position: absolute;
    background: red;
}

展开4.gif

As you can see, the small red square here is completely followed by the ellipsis. When the ellipsis appears, the red square must disappear, has been squeezed down , here is the parent overflow: hidden temporarily to see what the principle is.

展开5.gif

Then, you can just put this little red box to set a sufficient size , and reduce transparency, such as 100% * 100%

.text::after {
    content: '';
    width: 100%;
    height: 100%;
    position: absolute;
    background: red;
}

9202761e575d8627136547a61fc60edc.gif

. Now change the background to white (the same color as the parent), and add the parent 160a6878dc826a overflow: hidden

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

展开7.gif

Now look at the effect of clicking to expand

展开8.gif

After expanding now, I find that the button is missing (covered by the pseudo-element just now, and can't be clicked). What if I want to still be visible after clicking it? Add : checked state, hide the overlay when unfolding

.exp:checked+.text::after{
    visibility: hidden;
}

In this way, the function of hiding the expand button in the case of less text is realized

展开9.gif

The final complete code can be viewed at codepen. Multi-line expand collapse automatic hide ,


Compatible version can be viewed codepen multi-line expand collapse auto-hide (full compatible)

It should be noted that the compatible version can support IE 10+ (this is too much, it also supports IE), but because IE does not support codepen, the test IE can be copied and tested locally.

cc602bca-3538-466e-8dce-ee630ddd2635.gif

Four, summary and explanation

Overall, the focus is on layout , interaction in fact relatively easy to achieve overall cost is actually very low, there is no relatively uncommon properties, in addition to layout -webkit-Box looks like a little bug (after all, -webkit-kernel, Firefox just borrowed from it, some problems are unavoidable). Fortunately, the multi-line text truncation effect can be achieved in another way. The compatibility is quite good and basically fully compatible ( IE10+ ), here Organize the key points of realization

  1. The text wrapping effect is first considered float
  2. flex layout sub-elements can calculate the height by percentage
  3. Multi-line text truncation can also be combined with text wrapping effect with max-height simulation implementation
  4. State switching can use checkbox
  5. CSS change text can be generated using pseudo elements
  6. Make more use of CSS to conceal the "obstructive method"

The multi-line text expansion and collapse effect can be said to be a difficult problem in the industry. There are many js solutions, but they feel that they are not perfect. I hope this new idea of CSS solution can bring you different inspirations, thank you Read, like, favorite, forward~

阅读 6.8k

前端侦探
致力于有趣的前端探索~

偏用户体验的前端~

7.6k 声望
4.2k 粉丝
0 条评论
你知道吗?

偏用户体验的前端~

7.6k 声望
4.2k 粉丝
文章目录
宣传栏