复制含100条数据数组的ngFor导致CPU占用100%性能问题
解决滚动字幕(Marquee)CPU占用100%的问题
问题核心
你实现的金融/机场式滚动字幕,为了填满滚动区域将数据固定重复100次渲染,导致DOM元素量暴增,加上低效的滚动逻辑,直接拉满CPU占用。
优化方案
1. 精准控制数据重复次数
不要固定重复100次,而是根据容器宽度和单个列表项宽度计算刚好填满容器+额外一组的数量(保证滚动衔接无空白),避免生成不必要的DOM元素。
2. 使用CSS GPU加速动画
用transform: translateX()实现滚动,相比修改left/margin-left,transform会触发GPU加速,减少浏览器重排重绘,大幅降低CPU消耗。
3. 优化Angular变更检测
在*ngFor中添加trackBy函数,让Angular只更新变化的列表项,避免每次数据更新时销毁重建整个列表。
修改后的代码示例
HTML
<div class="app-marquee-container"> <div class="marquee-container"> <!-- 加载状态 --> <ng-container *ngIf="isLoadingQuoteData; else notIsLoadingQuoteData"> <ul class="marquee-list"> <li *ngFor="let _ of loadingPlaceholders"> <app-loading-skeleton type="TEXT" height="20px" width="140px" [isStyleInverse]="false"></app-loading-skeleton> </li> </ul> </ng-container> <!-- 滚动内容 --> <ng-template #notIsLoadingQuoteData> <ul class="marquee-list"> <li *ngFor="let data of repeatedQuoteData; trackBy: trackBySymbol" (click)="openBilletBySymbol(data.symbol)" > <span>{{ data.symbol }}</span> <span>{{ getMaskByPriceUnit(data.symbol, data.lastTradePrice, data.priceUnit) }}</span> <span [ngClass]="getPercentClassByValue(data.changePercent)"> {{ (data.changePercent / 100) | percent:'1.2-2' }} </span> </li> </ul> </ng-template> </div> </div>
TypeScript
quoteDataBySymbolSelected: IQuoteDataBySymbolSelected[] = []; repeatedQuoteData: IQuoteDataBySymbolSelected[] = []; loadingPlaceholders: string[] = new Array(15); // 按需调整加载占位数量 // 计算需要重复的次数 calculateRepeatCount(): number { const containerWidth = document.querySelector('.app-marquee-container')?.clientWidth || 0; const itemWidth = 140; // 单个li的宽度(和skeleton的width一致) if (containerWidth === 0 || this.quoteDataBySymbolSelected.length === 0) return 2; // 默认重复2组 const totalItemWidth = this.quoteDataBySymbolSelected.length * itemWidth; // 计算填满容器所需的组数,多一组保证滚动衔接无空白 return Math.ceil(containerWidth / totalItemWidth) + 1; } // 更新滚动数据(WebSocket回调或数据变更时调用) updateMarqueeData(): void { const repeatCount = this.calculateRepeatCount(); // 生成重复后的数据 this.repeatedQuoteData = Array(repeatCount).fill(null) .flatMap(() => [...this.quoteDataBySymbolSelected]); } // trackBy函数,优化Angular变更检测 trackBySymbol(index: number, item: IQuoteDataBySymbolSelected): string { return item.symbol; } // 组件初始化或窗口 resize 时重新计算 ngOnInit(): void { this.updateMarqueeData(); window.addEventListener('resize', () => this.updateMarqueeData()); } ngOnDestroy(): void { window.removeEventListener('resize', () => this.updateMarqueeData()); }
CSS(关键动画部分)
.app-marquee-container { overflow: hidden; width: 100%; position: relative; } .marquee-container { width: 100%; } .marquee-list { display: flex; list-style: none; padding: 0; margin: 0; /* 动画:从右往左滚动,时长可根据内容长度调整 */ animation: marquee 30s linear infinite; } .marquee-list li { min-width: 140px; margin-right: 16px; /* 项之间的间距 */ } @keyframes marquee { 0% { transform: translateX(100%); } 100% { transform: translateX(-100%); } } /* 加载状态禁用动画 */ .marquee-list:has(app-loading-skeleton) { animation: none; }
额外优化点
- WebSocket更新数据时,仅更新
quoteDataBySymbolSelected,再调用updateMarqueeData(),避免直接操作DOM。 - 如果需要动态调整滚动速度,可以通过CSS变量控制动画时长,比如定义
--marquee-duration: 30s,在TS中根据内容长度修改这个变量。
内容的提问来源于stack exchange,提问作者THIAGO DE BONIS
相关产品推荐
相关产品推荐

