感谢原文章提供了思路,本文在此基础上做了完善
由于本项目有多语言环境,所以要考虑切换语言后文本宽度的变化
思路:1. 在视图初始化后拿到dom元素的宽度
2. 用canvas计算文本大概的宽度进行比对看是否超出设定宽度
2. 超出的话再双指针遍历文本,用canvas计算出需要截断的下标后再组合显示
注意点:1.需要注意语言切换时的监听,且关注执行顺序
2.需要注意翻译后,视图更新后再去获取元素宽度
3.需要注意canvas计算与真实情况的误差(目前粗略计算了,可以再优化)
4.需要注意初始化时会执行translateService.onLangChange
import { Component, AfterViewInit, Input, ViewChild,OnDestroy,ChangeDetectorRef } from '@angular/core';
import {TranslateService} from '@ngx-translate/core';
import {PermissionToolkitService, StorageService, TranslatePoolComponent} from "pi-front-common-toolkits";
@Component({
selector: 'app-text-ellipsis',
templateUrl: './text-ellipsis.component.html',
styleUrls: ['./text-ellipsis.component.less']
})
export class TextEllipsisComponent extends TranslatePoolComponent implements AfterViewInit, OnDestroy {
constructor(
public translateService: TranslateService,
private cdr: ChangeDetectorRef // 注入 ChangeDetectorRef
) {
super(translateService);
}
//在视图初始化之后才能拿到dom元素,onOninit的时候ChildView还没执行完成,拿不到dom元素
ngAfterViewInit(): void {
//由于translateService.instant方法会在ngAfterViewInit之后执行,所以要用translateService.get(this.newText)的回调
//拿到翻译后的标题,然后拿到dom元素,在dom元素大小变化时更新内容
this.updateTitle();
// 监听语言切换事件
this.langChangeSub = this.translateService.onLangChange.subscribe(() => {
console.log('langChangeSub');
this.updateTitle(); // 语言切换时,重新获取翻译并更新内容
});
}
updateTitle(): void {
this.translateService.get(this.title).subscribe(translatedTitle => {
this.dom = this.titleRef?.nativeElement || null;
if (this.dom) {
const observer = new ResizeObserver(() => { // 在大小变化时更新内容
this.newText = translatedTitle;
console.log(this.newText)
this.truncateText(this.newText,this.dom);
});
observer.observe(this.dom);
}
});
}
ngOnDestroy(): void {
// 取消订阅语言切换事件,防止内存泄漏
if (this.langChangeSub) {
this.langChangeSub.unsubscribe();
}
}
@Input() title: string = '' ;
@ViewChild('titleRef') titleRef: any;
@Input() maxWidth: string = '100%';
langChangeSub = null;
// 标题盒子dom
dom = null;
newText = this.title;
// 获取dom元素的padding值
getPadding(el) {
const domCss = window.getComputedStyle(el, null);
const pl = Number.parseInt(domCss.paddingLeft, 10) || 0;
const pr = Number.parseInt(domCss.paddingRight, 10) || 0;
return {
left: pl,
right: pr
}
}
getTextLength(dom: HTMLElement): { status: boolean, width: number } {
const fullTextWidth = this.calculateTextWidth(this.newText, dom);
// 获取容器宽度并减去 padding
const { left, right } = this.getPadding(dom);
const paddingWidth = left + right;
const availableWidth = dom.clientWidth - paddingWidth;
return {
status: fullTextWidth > availableWidth, // 文本是否超出容器
width: availableWidth // 容器的可用宽度
};
}
truncateText(text: string, dom: HTMLElement): void {
const { status, width } = this.getTextLength(dom);
let result = text;
if (status) {
// 省略号的宽度
const ellipsis = '...';
const ellipsisWidth = this.calculateTextWidth(ellipsis, dom);
let leftText = '';
let rightText = '';
let leftIndex = 0;
let rightIndex = text.length - 1;
// 逐步截取左右两边的字符,直到符合容器的宽度
while (leftIndex < rightIndex) {
const tempLeft = leftText + text[leftIndex];
const tempRight = text[rightIndex] + rightText;
const combinedText = `${tempLeft}${ellipsis}${tempRight}`;
const combinedWidth = this.calculateTextWidth(combinedText, dom) + width / 14; //误差值为宽度的1/14左右
if (combinedWidth <= width) {
leftText = tempLeft;
rightText = tempRight;
leftIndex++;
rightIndex--;
} else {
break;
}
}
result = `${leftText}${ellipsis}${rightText}`;
}
dom.innerText = result;
}
calculateTextWidth(text: string, dom: HTMLElement): number {
// 获取 DOM 的字体样式
const computedStyle = window.getComputedStyle(dom);
const fontSize = computedStyle.fontSize;
const fontFamily = computedStyle.fontFamily;
// 创建一个 canvas 进行测量
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// 设置与目标 DOM 相同的字体样式
context.font = `${fontSize} ${fontFamily}`;
// 使用 canvas 的 measureText 方法测量文本宽度
const textWidth = context.measureText(text).width;
return textWidth;
}
}
<div class="title" #titleRef [ngStyle]="{width: maxWidth}">{{newText | translate}}</div>