需求
如图要实现下面的效果
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>