Expanding and collapsing multi-line text is a very common interaction, as shown in the following figure
The main difficulties in realizing this type of layout and interaction are as follows:
- Expand Collapse button in the lower right corner of multiline text
- Toggle between "expand" and "collapse" states
- When the text does not exceed the specified number of lines, the "Expand Collapse" button is not displayed
To be honest, it is not easy to look at this layout alone before, even with JavaScript (you need to calculate the width of the text to dynamically intercept the text, which is what vue-clamp does), not to mention the interaction and judgment logic below, but After some pondering, in fact, pure CSS can also be perfectly realized. Let's take a step by step to see how to achieve it~
1. The "Expand Collapse" button in the lower right corner
Many design students like this design. Put the button in the lower right corner and mix it with the text instead of a separate line, which may be more comfortable and beautiful visually. Let's take a look at multi-line text truncation first, this is relatively simple
- Multi-line text truncation
Suppose there is such an html structure
<div class="text">
浮动元素是如何定位的
正如我们前面提到的那样,当一个元素浮动之后,它会被移出正常的文档流,然后向左或者向右平移,一直平移直到碰到了所处的容器的边框,或者碰到另外一个浮动的元素。
</div>
Multi-line text is beyond omission. Everyone should be familiar with this. Line-clamp is mainly used. The key styles are as follows
.text {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
- Bottom right corner wrapping effect
When it comes to text wrapping effects , you can generally think of floating floats . 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;
/*其他装饰样式*/
}
If set to right float
.btn {
float: right;
/*其他装饰样式*/
}
At this time, there is already a surround effect, but it is located in the upper right corner. How to move the button to the lower right corner? try margin first
.btn {
float: right;
margin-top: 50px;
/*其他装饰样式*/
}
It can be seen that although the button is in the lower right corner, the text does not have any space above the button.
Although the margin can't 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
}
Now that the button is to the left of the pseudo-element, how do I move it to the bottom? Very simple, clear the floating clear: both;
.btn {
float: right;
clear: both;
/*其他装饰样式*/
}
It can be seen that the text is now completely wrapped around the two floating elements on the right. As long as the width of the pseudo-element with the red background is set to 0 (or no width is set, the default is 0), the effect of wrapping in the lower right corner is achieved.
.text::before{
content: '';
float: right;
width: 0; /*设置为0,或者不设置宽度*/
height: 50px;/*先随便设置一个高度*/
}
- dynamic height
Although the bottom right and surround are completed above, the height is fixed. How to set it dynamically? Calc calculation can be used here, 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);
}
It's a pity, it doesn't seem to have any effect, open the console and see, it turns out that calc(100% - 24px) calculates the height as 0
The reason is actually easy to think of. It is the problem of 100% height failure . There are many online analyses of this kind of problem. The usual solution is to specify a height for the parent, but the height here changes dynamically, and there is also an expanded state. , the height is even more unpredictable, so setting the height is not advisable.
In addition, there is actually another way, that is to use flex layout . The approximate method is that in the sub-items of the flex layout , the change height can be calculated by percentage. For details, please refer to the description of css-flexbox in w3.org
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, you need to wrap .text here, 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, the principle is similar
In this way, the calculated height just now takes effect, and the number of lines of text is changed, which is also located in the lower right corner~
In addition, dynamic height can also be implemented with a negative margin (the 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 effect of wrapping in the lower right corner is basically completed, and the ellipsis is also before the expand button. For the complete code, you can view the multi-line wrapping effect in the lower right corner of codepen.
4. Compatibility with other browsers
The above implementation is the most perfect way to handle it. I originally thought that compatibility was not a big problem. After all, only text truncation and floating were used. Although -webkit-line-clamp is a -webkit- prefix, it is also supported by firefox . When I opened it, I was dumbfounded. Safari and firefox were all messed up. !
This is a bit uncomfortable, has all the previous efforts been in vain? It is impossible to ignore these two, otherwise it can only be a demo, which cannot be used in a production environment.
Quickly open the console to see what is the reason. After some searching, it turned out to be display: -webkit-box ! After setting this property, the original text seems to become a whole block, and the floating element cannot produce the wrapping effect. After removing it, the floating is normal.
So the question is: how to achieve multi-line truncation without display: -webkit-box?
In fact, the above efforts have achieved the effect of wrapping in the lower right corner. If you set a maximum height when you know the number of lines, will you also complete multi-line truncation? In order to facilitate setting the height, you can add a line height line-height, if you need to set it to 3 lines, then set the height 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 lines, the commonly used number of lines can be separated through 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>
It can be seen that it is basically normal, except that there is no ellipsis, now add the ellipsis, just before the expand button, it can be implemented with pseudo elements
.btn::before{
content: '...';
position: absolute;
left: -10px;
color: #333;
transform: translateX(-100%)
}
In this way, the compatible layout of Safari and Firefox is basically completed, the complete code can be viewed in the lower right corner of codepen Multi-line expansion wrapping effect (full compatibility)
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 an input , then replace the previous button with a label, and associate it with the for attribute
<div class="wrap">
<input type="checkbox" id="exp">
<div class="text">
<label class="btn" for="exp">展开</label>
浮动元素是如何定位的
正如我们前面提到的那样,当一个元素浮动之后,它会被移出正常的文档流,然后向左或者向右平移,一直平移直到碰到了所处的容器的边框,或者碰到另外一个浮动的元素。
</div>
</div>
In this way, when clicking on the label , the input element is actually clicked. Now let's add two states, namely, only display 3 lines and no limit on the number of lines.
.exp:checked+.text{
-webkit-line-clamp: 999; /*设置一个足够大的行数就可以了*/
}
Compatible versions can directly set the maximum height max-height to a larger value, or directly set it to none
.exp:checked+.text{
max-height: none;
}
There is also a small problem here. The "Expand" button should become "Collapse" after being clicked. How to modify it?
There is a trick. Whenever you need to dynamically modify the content, you can use the pseudo-class content generation technology. The specific method is to remove or hide the text in the button and use pseudo-elements to generate
<label class="btn" for="exp"></label><!--去除按钮文字-->
.btn::after{
content:'展开' /*采用content生成*/
}
Added : checked status
.exp:checked+.text .btn::after{
content:'收起'
}
The compatible version cannot be automatically hidden because the preceding ellipsis is simulated, so additional processing is required.
.exp:checked+.text .btn::before {
visibility: hidden; /*在展开状态下隐藏省略号*/
}
The effect is basically the same as that at the beginning of this article. The complete code can be viewed in codepen Multi-line Expand Collapse Interaction ,
Compatible version can view codepen multi-line expand collapsing interaction (full compatibility)
Another point, if you set a suitable value for max-height , note that it is a suitable value . For the specific principle, please refer to CSS tricks and tricks: dynamic height transition animation , and transition animation can also be added.
.text{
transition: .3s max-height;
}
.exp:checked+.text{
max-height: 200px; /*超出最大行高度就可以
}
Third, the judgment of the number of lines of text
The above interaction has basically met the requirements, but there will still be problems. For example, when there is less text , there is no truncation at this time, that is, there is no ellipsis, but the "Expand" button is still in the lower right corner. How to hide it?
Usually the js solution is very easy, just compare the scrollHeight and clientHeight of the element, and then add the corresponding class name. Below is pseudo code
if (el.scrollHeight > el.clientHeight) {
// 文本超出了
el.classList.add('trunk')
}
So, how does CSS implement this kind of judgment?
To be sure, CSS does not have this kind of logical judgment, and most of us need to use "obfuscation" to achieve it from other angles. For example, in this scenario, when there is no truncation, it means that the text is completely visible. At this time, if an element (small red square) is added at the end of the text , in order not to affect the original layout, absolute positioning is set here.
.text::after {
content: '';
width: 10px;
height: 10px;
position: absolute;
background: red;
}
As you can see, the red square here is completely followed by the ellipsis. When the ellipsis appears, the small red square must disappear because it has been squeezed out . Here, you can see the principle by temporarily hiding the parent overflow: hidden .
Then, you can set the size of the small red square just now to a large enough size and reduce the transparency, such as 100% * 100%
.text::after {
content: '';
width: 100%;
height: 100%;
position: absolute;
background: red;
}
As you can see, the red block covers the lower right corner, and now the background is changed to white (the same background color as the parent), and the parent overflow: hidden is added again
.text::after {
content: '';
width: 100%;
height: 100%;
position: absolute;
background: #fff;
}
Now take a look at the effect of clicking to expand
Now after expanding it, it is found that the button is missing (covered by the pseudo-element just now, and cannot be clicked), what if you want to still be visible after clicking? Add a :checked state to hide the overlay when expanding
.exp:checked+.text::after{
visibility: hidden;
}
In this way, the function of hiding the expand button when there is less text is realized
The final complete code can be viewed in codepen Multi-line Expand Collapse Auto Hide ,
Or CSS multi-line expansion collapse automatically hide (runjs.work)
Compatible version can view codepen multi-line expand collapsing automatically hide (full compatibility)
If you can't open codepen access, you can visit this codepen. Multi-line expansion and collapsing and automatic hiding (full compatibility) (runjs.work)
RunJS , front-end code creation and sharing online.
It should be noted that the compatible version can support IE 10+ (this is too much, it still supports IE), but since IE does not support codepen, the test IE can be copied and tested locally.
4. Summary and Explanation
In general, the focus is still on the layout , the interaction is relatively easy, the overall implementation cost is actually very low, and there are no relatively uncommon attributes, except for the layout -webkit-box seems to be a bit buggy (after all, it is -webkit-kernel , Firefox just borrowed from it, there are inevitably some problems), fortunately, there is another way to achieve multi-line text truncation effect, the compatibility is quite good, basically fully compatible ( IE10+ ), here are the key points of implementation
- Text wrapping effects first consider floating float
- flex layout child elements can calculate height by percentage
- Multi-line text truncation can also be simulated with max-height combined with the text wrapping effect
- State switching can be done with the help of checkboxes
- CSS changes text can be generated using pseudo-elements
- More use of CSS to block the "obfuscation"
The multi-line text expansion and collapsing effect can be said to be a long-standing problem in the industry. There are many js solutions, but they are not perfect. I hope this new CSS solution can bring you different inspirations, thank you Read, welcome to like, favorite, forward~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。