vue 裡實現多行文字省略
阿新 • • 發佈:2021-01-08
需求
如圖要實現下面的效果
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>