If the text exceeds N lines, collapse the content and display "... View all"

Source: https://wintc.top/article/58

It is a common requirement that multiple lines of text exceed the specified number of lines, hide the excess part and display "... View all". Some people on the Internet have implemented similar functions, but they still want to write and see by themselves, so they wrote a Vue component. This paper briefly introduces the implementation idea.

Students who meet this requirement can try this component to support npm installation and use:

Component address: https://github.com/Lushenggang/vue-overflow-ellipsis

Online experience: https://wintc.top/laboratory/#/ellipsis

1, Requirement description

A text with variable length can display up to n lines (e.g. 3 lines), and no more than n lines can be displayed normally; If there are more than n lines, buttons such as "expand" or "view all" will be displayed at the end of the last line. Click the button to expand and display all contents, or jump to other pages to display all contents.

The expected results are as follows:

 

2, Implementation principle

Pure CSS is difficult to realize this function perfectly, so it has to be realized with the help of JS. The implementation ideas are generally similar. They are to judge whether the content exceeds the specified number of lines, intercept the first X characters of the string, and then splice it with "... View all". Here x is the intercept length, which needs to be calculated dynamically.

There are several problems to be solved in order to realize the above scheme:

    • How to judge whether the text exceeds the specified number of lines
    • How to calculate the length of string interception
    • Dynamic response, including response to page layout change, string change, specified line number change, etc

Let's study these problems in detail.

1. How to judge whether a paragraph of text exceeds the specified number of lines?

First, solve a small problem: how to calculate the height of the specified number of rows? My first thought is to use the rows attribute of textarea, specify the number of rows, and then calculate the height of textarea. Another method is to multiply the calculated value of row height by the number of rows to obtain the height of the specified number of rows. I haven't tried this method, but it must be feasible.

It solves the problem of specifying the height of the number of lines. It is easy to calculate whether a paragraph of text exceeds the specified number of lines. We can use absolute positioning absolute to separate the textarea with the specified number of lines from the document flow and put it below the text, and then compare the bottom of the text container with the bottom of the textarea. If the bottom of the text container is lower, it means that the specified number of lines is exceeded. This judgment can be passed getBoundingClientRect The interface obtains the location and size information of the two containers, and then compares the bottom attribute in the location information.

The DOM structure can be designed as follows:

  <div class="ellipsis-container">
    <div class="textarea-container">
      <textarea rows="3" readonly tabindex="-1"></textarea>
    </div>
    {{ showContent }} <-- showContent Represents the intercepted part of the string --> 
    ... See more
  </div>

 

Then use CSS to Control Textarea so that it is separated from the document flow and cannot be seen and triggered by mouse events (readonly and tabIndex attributes in textarea tag are necessary):

.ellipsis-container
  text-align left
  position relative
  line-height 1.5
  padding 0 !important
  .textarea-container
    position absolute
    left 0
    right 0
    pointer-events none
    opacity 0
    z-index -1
    textarea
      vertical-align middle
      padding 0
      resize none
      overflow hidden
      font-size inherit
      line-height inherit
      outline none
      border none

2. How to calculate the intercept length x of string -- bilateral approximation method (dichotomy idea)

As long as we can judge whether a paragraph of text exceeds the specified number of lines, we can dynamically try to intercept the string until we find the appropriate truncation length X. This length is sufficient to truncate the string from the position of X. the first half + "... View all" and other words will not exceed the specified number of lines N, but if you intercept one more word, it will exceed N lines. The most intuitive idea is to traverse directly, so that x increases from 0 to the total length of the displayed text. For each x value, calculate whether the text exceeds N lines. If it does not exceed, continue to traverse. If it exceeds, obtain the appropriate length x - 1 and jump out of the loop. Of course, you can also let x decrease from the total length of the text.

However, the biggest problem here is the backflow and redrawing of the browser. Because each time we intercept a string, we need the browser to render it again to get whether it exceeds N lines. In this process, the browser redrawing or reflow is triggered, and each cycle will be triggered. For normal requirements, assuming that the value of N is 3, it is likely that each calculation will lead to more than 50 redraws or backflows. The performance consumed in the middle is still very large, which may be tens of milliseconds or even hundreds of milliseconds. This calculation process should be completed in one task (commonly known as "macro task"), otherwise "exceptions" will appear in the calculation process, so it can be said that the calculation process is blocked. Therefore, the total calculation time must be controlled to a very low level, that is, the number of calculations must be reduced.

Consider using the "bilateral approximation method" (or "dichotomy") Find the appropriate interception length x, which greatly reduces the number of attempts. For the first time, take the text length as the interception length to calculate whether it exceeds N lines. If not, the calculation will be stopped; If it exceeds, take 1 / 2 of the length to intercept. If it does not exceed N lines at this time, continue to search in half from 1 / 2 of the length to the length of the text. If it exceeds, continue to search in half from 0 to 1 / 2 of the length of the text. Until the difference between the start value and the end value of the search interval is 1, the start value is the desired value. See the complete code below for the specific implementation.

3. Monitor page changes

For Vue projects, the string, number of lines, etc. of the incoming component may change at any time. You can watch these properties change, and then recalculate the intercept length. On the other hand, for the page layout, the page layout may change due to the addition, deletion or style change of other page elements, affecting the width of the text container. At this time, the interception length should also be recalculated.

Listen for the change of the width of the text container. You can consider using ResizeObserver Each version of ie does not support npm, but the compatibility of each version of IE is not good enough element-resize-detector To monitor (very easy to use) 👍).

 

3, Code implementation

The complete code implementation is as follows:

<template>
  <div class="ellipsis-container">
    <div class="textarea-container" ref="shadow">
      <textarea :rows="rows" readonly tabindex="-1"></textarea>
    </div>
    {{ showContent }}
    <slot name="ellipsis" v-if="(textLength < content.length) || btnShow">
      {{ ellipsisText }}
      <span class="ellipsis-btn" @click="clickBtn">{{ btnText }}</span>
    </slot>
  </div>
</template>

<script>
import resizeObserver from 'element-resize-detector'
const observer = resizeObserver()

export default {
  props: {
    content: {
      type: String,
      default: ''
    },
    btnText: {
      type: String,
      default: 'open'
    },
    ellipsisText: {
      type: String,
      default: '...'
    },
    rows: {
      type: Number,
      default: 6
    },
    btnShow: {
      type: Boolean,
      default: false
    },
  },
  data () {
    return {
      textLength: 0,
      beforeRefresh: null
    }
  },
  computed: {
    showContent () {
      const length = this.beforeRefresh ? this.content.length : this.textLength
      return this.content.substr(0, this.textLength)
    },
    watchData () { // Use a calculated attribute to uniformly observe the attribute changes that need attention
      return [this.content, this.btnText, this.ellipsisText, this.rows, this.btnShow]
    }
  },
  watch: {
    watchData: {
      immediate: true,
      handler () {
        this.refresh()
      

Posted by Mad_Mike on Tue, 03 May 2022 15:33:47 +0300