You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

复制含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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 14:47:43