1. 程式人生 > 其它 >vue 裡實現多行文字省略

vue 裡實現多行文字省略

技術標籤:07-Vuevue

需求

如圖要實現下面的效果

4行有插槽的

在這裡插入圖片描述

4行無插槽純文字的

在這裡插入圖片描述

注意點

比較重要的方法就是:測量文字長度:返回測量的文字的寬度measureText(measureStr)

這個我之前在一篇部落格裡提過【vue裡怎麼實現文字溢位才顯示title提示】;

自己可以新增slot,也可以直接純文字

注意點: slot 的元素必須小於一行的寬度

需要配置的可以自己配置一些引數

// 行數
lineNum: { 
  type: Number,
  default: 2
}, 
// 行的內容
lineContent: { 
  type: String,
  default
: '-' }, // 文字樣式 textStyle: { type: Object, default: () => { return { 'font-size': '14px', 'font-weight': 'bold', 'line-height': '22px', } } }

用法

<!-- 純文字:預設兩行、自己可以 lineNum 設定多行 -->
<self-adaption-line :lineContent="lineContent"></self-adaption-line
>
<!-- 有插槽 --> <self-adaption-line :lineContent="lineContent"> <div class="icon-item"> <el-tooltip effect="dark" content="測試1。" placement="right"> <img src="@/2020/kaimo/icon-1.svg"/> </el-tooltip
>
</div> <div class="icon-item"> <el-tooltip effect="dark" content="測試2測試2。" placement="right"> <img src="@/2020/kaimo/icon-2.svg"/> </el-tooltip> </div> <div class="icon-item"> <span>測試1</span> </div> <div class="icon-item"> <span>測試2測試2</span> </div> </self-adaption-line>

程式碼實現

<template>
<div ref="adaptionWrap" class="self-adaption-line-wrap">
  <div ref="lineWrap" class="line-wrap" :style="[textStyle]">
    <template v-for="(item, index) in calculateLineList">
      <!-- 有插槽,文字超出一行 -->
      <template v-if="hasSlots">
        <!-- 需要判斷最後一行文字與插槽資料是否需要撐開換行 -->
        <template v-if="hasSlotsTextWarp(item)">
          <!-- 最後一行 -->
          <div :key="item.lineIndex" class="slots-text-wrap has-text is-end">{{item.lineWords}}</div>
          <slot></slot>
        </template>
        <!-- 不需要撐開換行,新增省略號即可 -->
        <template v-else>
          <!-- 最後一行新增 ellipsis -->
          <div v-if="index + 1 === calculateLineList.length" :key="item.lineIndex"
            :class="['has-slots-text', {'has-ellipsis is-end': index + 1 === calculateLineList.length}]"
          >
            <div :class="['has-text', {'ellipsis is-end': index + 1 === calculateLineList.length}]">{{item.lineWords}}</div>
            <slot></slot>
          </div>
          <!-- 不是最後一行 -->
          <div v-else class="has-text" :key="item.lineIndex">{{item.lineWords}}</div>
        </template>
      </template>
      <!-- 無插槽,純文字 -->
      <template v-else>
        <div :class="['has-text', {'ellipsis is-end': index + 1 === calculateLineList.length}]" 
          :key="item.lineIndex"
        >{{item.lineWords}}</div>
      </template>
    </template>
  </div>
</div>
</template>

<script>
export default {
  name: 'selfAdaptionLine',
  props: {
    // 行數
    lineNum: { 
      type: Number,
      default: 2
    }, 
    // 行的內容
    lineContent: { 
      type: String,
      default: '-'
    },
    // 文字樣式
    textStyle: {
      type: Object,
      default: () => {
        return {
          'font-size': '14px',
          'line-height': '22px',
        };
      }
    }
  },
  data() {
    return {
      hasSlots: Boolean(this.$slots.default), // 是否有插槽資料
      fontSizeLineWrap: '', // lineWrap 的 font-size
      fontSizeLineWrapNumber: 0, // lineWrap 的 font-size 沒有px
      fontFamilyLineWrap: '', // lineWrap 的 font-family
      fontWeightLineWrap: '', // lineWrap 的 font-weight
      allWordsLen: 0, // 全部文字測量的長度
      maxLineWidth: 0, // 一行容器能容納的文字長度(自適應盒子的寬度)
      wordLen: '', // 每個字的平均寬度
      averageLineWordNum: 0, // 每行平均能放下的字數
      autoFinish: 0, // 當微調函式執行超過10次時,應當結束掉
      calculateLineList: [], // 計算出來的陣列,裡面記錄每一個對應的文字個數以及內容
    };
  },
  watch: {
    lineContent: {
      handler(n) {
        if(n !== '-') {
          // 初始化渲染
          this.$nextTick(() => {
            this.initSet(); // 初始化設定
            this.initRender(); // 初始化渲染
            console.log('初始化渲染完畢--->', this.getAdaptionWrapWidth());
          });
          // 處理初始化渲染完畢的寬度變動問題
          let timer = setTimeout(() => {
            let newWidth = this.getAdaptionWrapWidth();
            console.log('初始化渲染完畢setTimeout--->', this.maxLineWidth, newWidth);
            if(this.maxLineWidth !== newWidth) {
              // 一行容器能容納的文字長度(自適應盒子的寬度)
              this.maxLineWidth = newWidth;
              // 每行平均能放下的字數
              this.averageLineWordNum = Math.floor(this.maxLineWidth / this.wordLen);
              // 清空資料
              this.calculateLineList = [];
              // 初始化渲染
              this.initRender();
            }
            clearTimeout(timer);
          }, 300);
        }
      },
      immediate: true
    }
  },
  methods: {
    // 判斷最後一行是否要新增省略號
    hasSlotsTextWarp(item) {
      let flag = false;
      // 文字內容沒有達到 指定的 lineNum 時,最後一行的文字與插槽內容超出應該換行
      if(item.lineIndex === this.calculateLineList.length && this.calculateLineList.length < this.lineNum) {
        flag = true;
      }
      return flag;
    },
    // 獲取自適應盒子的寬度 adaptionWrap
    getAdaptionWrapWidth() {
      return this.$refs.adaptionWrap.getBoundingClientRect().width;
    },
    // 初始化設定
    initSet() {
      // 獲取 lineWrap 的 font-size
      this.fontSizeLineWrap = document.defaultView.getComputedStyle(this.$refs.lineWrap)['font-size'];
      this.fontSizeLineWrapNumber = Number(this.fontSizeLineWrap.split('px')[0]);
      // 獲取 lineWrap 的 font-family
      this.fontFamilyLineWrap = document.defaultView.getComputedStyle(this.$refs.lineWrap)['font-family'];
      // 獲取 lineWrap 的 font-weight
      this.fontWeightLineWrap = document.defaultView.getComputedStyle(this.$refs.lineWrap)['font-weight'];
      // 全部文字測量的長度
      this.allWordsLen = this.measureText(this.lineContent);
      // 一行容器能容納的文字長度(自適應盒子的寬度)
      this.maxLineWidth = this.getAdaptionWrapWidth();
      // 每個字的平均寬度
      this.wordLen = this.allWordsLen / this.lineContent.length;
      // 每行平均能放下的字數
      this.averageLineWordNum = Math.floor(this.maxLineWidth / this.wordLen);
      console.log(`allWordsLen:${this.allWordsLen},maxLineWidth:${this.maxLineWidth}`);
      console.log(`每行平均能放下的字數: ${this.maxLineWidth / this.wordLen} `);
      console.log(`每個字的平均寬度:${this.wordLen},每行能放下 ${this.averageLineWordNum} 個字`);
    },
    // 初始化渲染:判斷是否有插槽資料
    initRender() {
      // 生成顯示的文字內容
      for(let i = 0; i < this.lineNum; i++) {
        this.calculateLineNum(i + 1, this.averageLineWordNum);
      }
      console.log('calculateLineList---->', this.calculateLineList);
    },
    // 測量文字長度:返回測量的文字的寬度
    measureText(measureStr) {
      let ctx = document.createElement('canvas').getContext("2d");
      // 獲取 font-size
      let fontSize = this.textStyle['font-size'] || this.fontSizeLineWrap;
      // 獲取 font-family
      let fontFamily = this.textStyle['font-family'] || this.fontFamilyLineWrap;
      // 獲取 font-weight 
      let fontWeight = this.textStyle['font-weight'] || this.fontWeightLineWrap;
      // 設定 font 的樣式
      ctx.font = `${fontWeight} ${fontSize} ${fontFamily}`;
      console.log(ctx);
      return ctx.measureText(measureStr).width;
    },
    /**
     * @description 調整一行真實的顯示字的個數
     * @param {Number} lineIndex: 第幾行
     * @param {Number} curIndex: 計算當前行數從什麼地方開始擷取
     * @param {Number} averageLineWordNum: 每行平均能放下的字數
    */
    adjustLineWordNum(lineIndex, curIndex, averageLineWordNum) {
      let lineWordLen = 0; // 調整之後的內容長度
      let lineWords = ''; // 調整之後的內容
      // 測量一下 averageLineWordNum 個數的寬度
      let averageContent = this.lineContent.substring(curIndex, averageLineWordNum + curIndex);
      // 剩餘的寬度
      let surplusLen = this.measureText(this.lineContent.substring(curIndex));
      console.warn('剩餘的寬度--->', surplusLen);
      console.warn(`第${lineIndex}行,curIndex:${curIndex}`, averageLineWordNum, '內容--->', averageContent);
      let averageWidth = this.measureText(averageContent);
      console.log('averageWidth:', averageWidth, 'maxLineWidth:', this.maxLineWidth);
      // 判斷是否在閾值裡 fontSizeLineWrapNumber
      let isThreshold = Math.abs(averageWidth - this.maxLineWidth) < this.fontSizeLineWrapNumber;
      console.log('判斷是否在閾值裡-->', averageWidth - this.maxLineWidth, isThreshold);
      // 需要有內容,並且剩餘的長度大於一行的寬度
      if(averageContent && surplusLen > this.maxLineWidth) {
        // 判斷是需要增加還是減少,如果 autoFinish 大於等於 10, 應該結束微調函式
        if (averageWidth > this.maxLineWidth && this.autoFinish < 10) {
          this.autoFinish++;
          return this.adjustLineWordNum(lineIndex, curIndex, averageLineWordNum - 1);
        } else if (averageWidth < this.maxLineWidth && !isThreshold && this.autoFinish < 10) {
          this.autoFinish++;
          return this.adjustLineWordNum(lineIndex, curIndex, averageLineWordNum + 1);
        } else {
          // 結束微調時應判斷這行文字寬度是否大於一行的寬度,是的話需要減一
          lineWordLen = averageWidth > this.maxLineWidth ? averageLineWordNum - 1 : averageLineWordNum;
          // 判斷是不是最後一行,是最後一行就顯示剩餘的所有內容
          let tempIndex = lineIndex === this.lineNum ? this.lineContent.length : lineWordLen + curIndex;
          lineWords = this.lineContent.substring(curIndex, tempIndex);
        }
        console.warn('this.autoFinish-->', this.autoFinish, averageLineWordNum, averageWidth , this.maxLineWidth);
      } else {
        // 表名資料長度不夠,不需要在調整,直接賦值
        lineWords = averageContent;
        lineWordLen = averageContent.length;
      }
      console.warn(`第${lineIndex}行`,lineWordLen, lineWords);
      return {
        lineWordLen: lineWordLen, // 真實的個數
        lineWords: lineWords, // 真實的個數內容
        averageWidth: averageWidth // averageLineWordNum 個數的寬度
      };
    },
    // 計算第 lineIndex 行顯示 lineContent內容的字數
    calculateLineNum(lineIndex, averageLineWordNum) {
      // 計算當前行數從什麼地方開始擷取
      let curIndex = 0;
      this.calculateLineList.forEach(el => {
        curIndex += el.lineWordLen;
      });
      console.log('curIndex--->', curIndex);
      // 獲取一行的真實個數
      let { lineWordLen, lineWords, averageWidth } = this.adjustLineWordNum(lineIndex, curIndex, averageLineWordNum);
      if(lineWords) {
        this.calculateLineList.push({
          lineIndex: lineIndex, // 第幾行
          averageLineWordNum: averageLineWordNum, // 一行的平均字數
          averageWidth: averageWidth, // averageLineWordNum 個數的寬度
          lineWordLen: lineWordLen, // 真實的個數
          lineWords: lineWords // 真實的個數內容
        });
      }
    }
  },
};
</script>

<style lang="scss">
.self-adaption-line-wrap {
  width: 100%;
  .line-wrap {
    word-break: break-all;
    vertical-align: middle;
    cursor: pointer;
    .slots-text-wrap {
      display: inline-block;
    }
    .has-ellipsis.has-slots-text {
      display: flex;
    }
    .has-text {
      white-space: nowrap;
    }
    .ellipsis {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
  }
}
</style>