Yan Wenbin

Original statement: This article is produced by members of Yuewen front-end team YFE, please respect the original, reprint please contact the public number (ID: yuewen_YFE) for authorization, and indicate the author, source and link.

Multiline text unwrapping is a very common interaction, as shown in the figure below

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

  • The Unwrap button in the lower right corner of the multiline text
  • Switch between “Expand” and “Collapse” states
  • When the number of lines of text does not exceed the specified number, the Collapse button is not displayed

To be honest, it is not easy to see this layout alone even with JavaScript (you need to calculate the width of the text to dynamically intercept the text, which vue-clamp does), not to mention the interaction and judgment logic below, but after some thinking, it can be done perfectly in pure CSS. Here’s a step by step look at how to achieve it

1. “Unfold and Fold up” button in the lower right corner

Many design students like this design, put the button in the bottom right corner, mixed with the text, rather than a single line, may be more visually comfortable and beautiful. Let’s take a look at multi-line truncation, which is easy

1. Multi-line text truncation

Suppose you have an HTML structure like this

<div class="text">As we mentioned earlier, when an element floats, it is moved out of the normal flow of the document and then shifted to the left or right until it hits the border of the container it is in, or hits another floating element.</div>
Copy the code

Multi-line text should be used in line clamp. The key styles are as follows

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

2. Surround effect in lower right corner

When it comes to wrapping text, you usually think of a float. Yes, don’t think of a float as a thing of the past. Let’s say I put a button down here, and I’ll float it

<div class="text">
  <button class="btn">an</button>As we mentioned earlier, when an element floats, it is moved out of the normal flow of the document and then shifted to the left or right until it hits the border of the container it is in, or hits another floating element.</div>
Copy the code
.btn {
  float: left;
  /* Other decorative styles */
}
Copy the code

If right float is set

.btn {
  float: right;
  /* Other decorative styles */
}
Copy the code

I already have itaroundHow to move the button to the lower right corner? Give it a trymargin

.btn {
  float: right;
  margin-top: 50px;
  /* Other decorative styles */
}
Copy the code

You can see that even though the button is in the bottom right corner, the text doesn’t wrap around the space above the button, so it leaves a lot of space, so there’s nothing to do?

Margin doesn’t solve the problem, but the entire text is still affected by the floating button. What if there are multiple floating elements? Here we use the pseudo-element ::before instead

.text::before{
  content: ' ';
  float: right;
  width: 10px;
  height: 50px;/* Set a random height */
  background: red
}
Copy the code

Now that the button is to the left of the pseudo-element, how do I move it down? Clear: both; It is ok

.btn {
  float: right;
  clear: both;
  /* Other decorative styles */
}
Copy the code

As you can see, the text is now completely wrapped around the two floating elements on the right. Just set the pseudo-element width of the red background to 0 (or otherwise, the default is 0) to wrap around the bottom right corner

.text::before{
  content: ' ';
  float: right;
  width: 0; /* Set the width to 0 or not */
  height: 50px;/* Set a random height */
}
Copy the code

3. Dynamic height

Although the upper right is completed with surround, but the height is fixed, how to dynamic setting? You can use calc to calculate this by subtracting 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);
}
Copy the code

Calc (100% – 24px) calculates height to 0, right

In fact, it is easy to think that the height of 100% failure problem, there are many online analysis of this kind of problem, the usual solution is to specify a height for the parent, but here the height is dynamic change, and there is expansion state, the height is unpredictable, so it is not desirable to set the height.

In addition, there is actually another way to use Flex layout. The general idea is that in the Flex layout subitem, you can calculate the change height by percentage, as described in w3.org for csS-Flexbox

If the flex item hasalign-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 a layer around.text and set up display: flex

<div class="wrap">
  <div class="text">
    <button class="btn">an</button>As we mentioned earlier, when an element floats, it is moved out of the normal flow of the document and then shifted to the left or right until it hits the border of the container it is in, or hits another floating element.</div>
</div>
Copy the code
.wrap{
  display: flex;
}
Copy the code

In practice, display: grid and display: -webkit-box are equally effective. The principle is similar

From there, the height of the calculation takes effect, changing the number of lines of text, also in the lower right corner

In addition, dynamic height can also be achieved with 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;
}
Copy the code

At this point, the lower right corner wrap effect is almost complete, and the ellipsis is also located in front of the expand button. You can see the complete code for codepen’s lower right corner multi-line wrap effect

4. Compatibility with other browsers

The above implementation is the perfect way to do this. -webkit-line-clamp is a prefix to -webkit-clamp, but firefox also supports it. After all, safari and Firefox are all in chaos.

This is a little uncomfortable, so much work in front of all for nothing? There is no way to ignore these two, otherwise it would be a demo and not usable in production.

Go to the console and see what’s going on. Display: -webkit-box! When this attribute is set, the original text appears to be a single block, and the floating element does not create a surround effect

So here’s the problem: how do you do multi-line truncation without display: -webkit-box?

In fact, the above efforts have achieved the effect of wrapping the lower right corner. If we set a maximum height knowing the number of rows, does the multi-line truncation also complete? To make it easier to set the height, you can add a line height, line-height, or if 3 rows are needed, 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.5 em;
  overflow: hidden;
}
Copy the code

For better control of the number of rows, the common number of rows can be isolated by the property selector (usually not too many), as shown below

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

And you can see that it’s almost normal, except that there’s no ellipsis, so now I’m going to add the ellipsis, just before the expand button, so I can do that with pseudo elements

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

In this way, the Safari and Firefox compatible layout is almost complete. The complete code can be viewed at codepen lower right corner of the multi-line wrap effect (full compatibility).

Two, “expand” and “pack up” two states

When you think of CSS state switching, you think of input type=”checkbox”. We need to use the same property here, first by adding an input, then replacing the previous button with a label, and associating it with the for attribute

<div class="wrap">
  <input type="checkbox" id="exp">
  <div class="text">
    <label class="btn" for="exp">an</label>As we mentioned earlier, when an element floats, it is moved out of the normal flow of the document and then shifted to the left or right until it hits the border of the container it is in, or hits another floating element.</div>
</div>
Copy the code

So, when you click on the Label, you’re actually clicking on the input element. Now add two states, one showing only three lines and one with no line limit

.exp:checked+.text{
  -webkit-line-clamp: 999; /* Set a large enough number of rows */
}
Copy the code

Compatible versions can directly set max-height to a larger value or set it to None

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

There is a small problem here. The “expand” button should change to “collapse” after being clicked. How to fix this?

One technique is to use pseudo-content generation technology when you need to dynamically modify content. The specific method is to remove or hide the text inside the button and use pseudo-element generation

<label class="btn" for="exp"></label><! -- Remove button text -->
Copy the code
.btn::after{
  content:'a'/* use content to generate */}Copy the code

Added: Checked status

.exp:checked+.text .btn::after{
  content:'put'
}
Copy the code

Compatible versions require extra processing because the previous ellipses are simulated and cannot be automatically hidden

.exp:checked+.text .btn::before {
    visibility: hidden; /* Hide the ellipsis */ when expanded
}
Copy the code

This is basically the same as at the beginning of this article. For the full code, you can view codepen Multi-line Expand and Collapse interaction. For the compatible version, you can view Codepen Multi-line Expand and Collapse Interaction (fully compatible).

One more thing, if you set max-height to an appropriate value, note that it is an appropriate value, the specific principle can refer to CSS wizarding techniques: dynamic height transition animation, also can add transition animation

.text{
  transition:.3s max-height;
}
.exp:checked+.text{
  max-height: 200px; /* Exceed the maximum row height */
}
Copy the code

Three, the number of lines of text judgment

The above interaction is basically adequate, but there are still problems. For example, when the text is small, there is no truncation, that is, there is no ellipsis, but the “expand” button is still in the lower right corner, how to hide?

In general, the js solution is easy to compare the scrollHeight and clientHeight of the element, and then add the corresponding class name. Here is the pseudocode

if (el.scrollHeight > el.clientHeight) {
  // The text is exceeded
  el.classList.add('trunk')}Copy the code

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 adopt a “smoke screen” to achieve it from another perspective. For example, in this scene, when there is no truncation, the text is fully visible. In this case, if you add an element (red square) to the end of the text, you set absolute positioning so that the original layout is not affected

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

As you can see, the little red square here is completely following the ellipsis. When the ellipsis appears, the little red square must disappear,Because I’m already being pushed down, here put the parentoverflow: hiddenWhat’s the principle of hiding it for a while

Then, you can make this little red square a big enough size, like 100% by 100%

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

As you can see, the red block covers the bottom right corner. Now change the background to white (the same color as the parent) and add overflow: hidden again

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

Now let’s see what happens when I click to expand

Now when I expand it, I see that the button is not there (it is covered by the pseudo-element and cannot be clicked). What if I want the button to remain visible after I click it? Add the checked state to hide the overlay when expanded

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

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

The final complete code can be viewed codepen multi-line collapse automatic hide, compatible version can be viewed Codepen multi-line collapse automatic hide (fully compatible)

Note that the compatible version supports IE 10+, but since IE does not support Codepen, testing IE can be replicated locally.

Iv. Summary and explanation

Overall, the focus is on the layout, the interaction is relatively easy, the overall implementation cost is actually very low, and there are no unusual properties, except for the layout -webkit-box, which seems to be buggy (after all, it’s the webkit-kernel, Firefox just took it from it, so there are some problems). Fortunately, there is another way to achieve multi-line text truncation. The compatibility is pretty good, almost fully compatible (IE10+). Here are the highlights of the implementation

  • First consider a float for text wrap effects
  • Flex layout child elements can calculate their height as a percentage
  • Multi-line text truncation can also be simulated with max-height in combination with text wrap effects
  • Status switching can be done with a checkbox
  • CSS change text can be generated using pseudo-elements
  • Use CSS to block “smoke screen”

Multiple lines of text to expand the effect can be said to be a big problem in the industry, there are a lot of JS solutions, but the feeling is not very perfect, I hope this new idea of CSS solution can bring you different inspiration, thank you for reading, welcome to like, collect, forward ~