vue 里实现多行文本省略

简介: vue 里实现多行文本省略

需求

如图要实现下面的效果



4行有插槽的

a09d563f50b04ab5ae4e852d447e1975.png


4行无插槽纯文本的

20210107204549277.png


注意点

比较重要的方法就是:测量文本长度:返回测量的文字的宽度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>




目录
相关文章
|
4天前
|
前端开发 JavaScript
Vue底层实现原理总结
Vue底层实现原理总结
8 0
|
8天前
|
JavaScript 前端开发 测试技术
使用 Vue CLI 脚手架生成 Vue 项目
通过 Vue CLI 创建 Vue 项目可以极大地提高开发效率。它不仅提供了一整套标准化的项目结构,还集成了常用的开发工具和配置,使得开发者可以专注于业务逻辑的实现,而不需要花费大量时间在项目配置上。
67 7
使用 Vue CLI 脚手架生成 Vue 项目
|
6天前
|
JavaScript
|
7天前
|
存储 JavaScript API
Vue状态管理深度剖析:Vuex vs Pinia —— 从原理到实践的全面对比
Vue状态管理深度剖析:Vuex vs Pinia —— 从原理到实践的全面对比
13 2
|
10天前
|
JavaScript 算法
“Error: error:0308010C:digital envelope routines::unsupported”启动vue项目遇到一个错误【已解决
“Error: error:0308010C:digital envelope routines::unsupported”启动vue项目遇到一个错误【已解决
11 1
|
10天前
|
JavaScript
error Component name “Login“ should always be multi-word vue/multi-word-component-names【已解决】
error Component name “Login“ should always be multi-word vue/multi-word-component-names【已解决】
25 1
|
12天前
|
JavaScript API
【vue实战项目】通用管理系统:信息列表,信息录入
【vue实战项目】通用管理系统:信息列表,信息录入
19 3
|
11天前
|
JavaScript API
【vue实战项目】通用管理系统:信息列表,信息的编辑和删除
【vue实战项目】通用管理系统:信息列表,信息的编辑和删除
26 2
|
3天前
|
JavaScript 前端开发
Vue躬行记(7)——渲染函数和JSX
Vue躬行记(7)——渲染函数和JSX
8 0
|
11天前
|
JavaScript 前端开发 Java
【vue实战项目】通用管理系统:作业列表
【vue实战项目】通用管理系统:作业列表
23 0