First of all, the idea of this article is not original, refer to: CSS implementation of multi-line text “expand and fold”

Last week, I saw that my colleagues in the company sent an article about the front end team of reading articles, which described how to realize the function of expanding and folding multi-line text with pure CSS (sure enough, all the reading articles are CSS leaders). It happened that this function was needed this week, so I made some packaging transformation on this basis and applied it to the company’s internal projects.

End result:

Business background

This time, we will make an external long link to evoke the deeplink of the corresponding WebView in the app. Meanwhile, we will generate a short link and display the historical conversion record with the Table component (ViewUI is used in this project). With strings that are close to 100 in length, the product wants to add a function that expands and collapses for aesthetic reasons.

Feature list

To implement multiline text expansion and collapse using pure CSS, you need to implement the following functions:

  • Multiline text truncation
  • “Expand and fold” button in lower right corner (surround effect)
  • Control the unfolding and folding state
  • Can be componentized to the Table component

Multiline text truncation

The traditional CSS scheme is already familiar:

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

But we’re not going to do truncation in this way, because if you do, you can expect a snap from the folded state to the expanded state, which is not silky and not cool. To have a transition animation, we decided to change the height of the text container + Transition. (There is also an important reason: the inability to handle browser compatibility issues. It can be found in the original article.)

There is a problem: most of the transition animation state of initial value and end value needs to be changed, but we don’t know that there are long text, not to mention high, difficult to wait for the component mount then call Element. Getboundingclientrect () to obtain high dynamic? No! Don’t forget our headline: CSS Only! There’s another property that can do this, and that’s max-height!

Although we don’t know how many lines of text there are, what the height is. However, the initial state can be controlled by using line-height, and the end state can achieve the same effect by giving a large enough max-height. (Of course, the value should not be too large, which will affect the animation effect. The height of the text container does not change from the initial limit to max-height, but the animation is executed at (end state max-height-initial state max-height)/time.

The relevant codes are as follows:

// Initial state.text {
    overflow: hidden;
    text-overflow: ellipsis;
    text-align: justify;
    position: relative;
    line-height: 1.5 em;
    max-height: 3em; / / limit2linetransition: 0.3 smax-height ease-in-out; } // Expand the state.text.open {
  max-height: 999px;
}
Copy the code

“Expand and fold” button in lower right corner (surround effect)

This is relatively simple, there are many ways to achieve, such as: place a button, its clear: both + pseudo-element flot: right to achieve the right side of the text around the effect; Width: 0; Height: calc(100% – button height) implements the button in the lower right corner of the text. The code is not included here, but can be viewed in the final complete code.

Control the unfolding and folding state

Use to expand and unpack the two states, and use the pseudo-content class to handle the text in both states. In two words, excellent!

Replace with :

<template>
  <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 is floated, it is moved out of the normal document flow and then shifted left or right until it hits the border of the container it is in, or another floating element.</div>
  </div>
</template>
<style lang="less">
.btn::after{
  content:'a'/* Use content to generate */.exp:checked+.text .btn::after{
  content:'put'
}
</style>
Copy the code

Can be componentized to the Table component

There are two basic issues to deal with when applying components to a project:

  • The number of lines initially limited can be configured
  • a<label />For corresponds to one<input />

If you want to configure the initial number of rows, you want to control the initial max-height. Since we set line-height to 1.5em, we accept a parameter through props to dynamically evaluate and bind max-height.

<div class="text" :id="'text' + id" :style="{' Max - height: ` $1.5} {line * em `}">
Copy the code
<script>
export default {
  props: {
    line: {
      type: Number.default: 2
    }
  }
}
</script>
Copy the code

After writing it, I ran and found that it didn’t work

This is easy! Important arrange it! (Although not recommended! This property only controls the transition animation, and does not cause global style pollution.

.exp:checked + .text {
  max-height: 999px ! important;
}
Copy the code

Perfect run!

Next, we deal with the problem that a for corresponds to a . This is easier. Generally, each piece of data has a unique ID. We dynamically generate an ID for and assign the for attribute to according to the same rules.

<input :id="'exp' + id" class="exp" type="checkbox" />
<div class="text" :id="'text' + id" :style="{' Max - height: ` $1.5} {line * em `}">
	<label class="btn" :for="'exp' + id"></label>
	{{ text }}
</div>
Copy the code

Take a look again:

A new problem arises: the use of the text container’s :after pseudo-class generates a large box-shadow to hide the expand and collapse button when the text is not truncated enough

{
    box-shadow: inset calc(100px - 999vw) calc(30px - 999vw) 0 0 #fff;
}
Copy the code

This background color is incompatible with the hover effect that comes with ViewUI.

Look at the ViewUI source code and add the same transition animation to the text container’s: After pseudo-class. Done!

// compatible with ivew hover&highlight styles.text::after{
  transiton: .2s box-shadow ease-in-out
}
.ivu-table-row-hover..ivu-table-row-highlight {
  .CollapseTextViewWrapper {
    .text::after {
      box-shadow: inset calc(100px - 999vw) calc(30px - 999vw) 0 0 #ebf7ff; }}}Copy the code

Finally, add a few more pieces of data to see the result:

Attach the complete code:

<template>
  <div class="CollapseTextViewWrapper">
    <input :id="'exp' + id" class="exp" type="checkbox" />
    <div class="text" :id="'text' + id" :style="{' Max - height: ` $1.5} {line * em `}">
      <label class="btn" :for="'exp' + id"></label>
      {{ text }}
    </div>
  </div>
</template>
<script>
export default {
  name: 'CollapseView'.props: {
    text: {
      type: String.default: ' ',},id: {
      type: Number,},line: {
      type: Number.default: 2,}}};</script>
<style lang="less">
.CollapseTextViewWrapper {
  display: flex;
  overflow: hidden;
  padding: 5px 0; /** hack for iView's table cell strange blank in vertical */
  .text {
    overflow: hidden;
    text-overflow: ellipsis;
    text-align: justify;
    position: relative;
    line-height: 1.5 em;
    transition: 0.3 s max-height ease-in-out;
  }
  .text::before {
    content: ' ';
    height: calc(100% - 18px);
    float: right;
  }
  .text::after {
    content: ' ';
    width: 999vw;
    height: 999vw;
    position: absolute;
    box-shadow: inset calc(100px - 999vw) calc(30px - 999vw) 0 0 #fff;
    transition:.2s box-shadow ease-in-out;
    margin-left: -100px;
  }
  .btn {
    position: relative;
    float: right;
    clear: both;
    margin-left: 20px;
    color: rgb(0.110.255);
    cursor: pointer;
  }
  .btn::after {
    content: 'a';
  }
  .exp {
    display: none;
  }
  .exp:checked + .text {
    max-height: 999px;
  }
  .exp:checked + .text::after {
    visibility: hidden;
  }
  .exp:checked + .text .btn::before {
    visibility: hidden;
  }
  .exp:checked + .text .btn::after {
    content: 'put';
  }
  .btn::before {
    content: '... ';
    position: absolute;
    left: -5px;
    color: # 333;
    transform: translateX(-100%); } // compatible with ivew hover&highlight styles.ivu-table-row-hover..ivu-table-row-highlight {
  .CollapseTextViewWrapper {
    .text::after {
      box-shadow: inset calc(100px - 999vw) calc(30px - 999vw) 0 0 #ebf7ff; }}}</style>
Copy the code