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

Angular 14从零实现虚拟滚动:itemsToDisplay数组功能疑问

Angular 无第三方库实现虚拟滚动核心逻辑说明

核心误解澄清

你提到的itemsToDisplay不是只加载一次——它需要随着滚动动态更新,这是虚拟滚动的核心:只渲染当前视口(加少量缓冲)的DOM元素,同时通过占位容器模拟整个列表的高度,让滚动条正常显示。如果只加载一次视口内的项,滚动时会看不到新内容,且滚动条长度也不对,这就是你遇到的问题。

实现关键变量与逻辑

先明确几个必须的变量(固定高度场景,新手优先用这种方式):

  • allItems:所有待展示的完整数据列表
  • itemHeight:单个列表项的固定高度(比如50px)
  • viewportHeight:滚动容器的可视高度(通过DOM元素获取)
  • visibleItemCount:视口能容纳的项数,计算方式:Math.ceil(viewportHeight / itemHeight),建议额外加2-3个缓冲项(避免滚动时出现空白)
  • scrollTop:滚动容器的滚动偏移量(监听scroll事件获取)
  • startIndex:当前视口第一个可见项的索引,计算方式:Math.floor(scrollTop / itemHeight)
  • itemsToDisplay:截取allItems中从startIndex到startIndex + visibleItemCount + 缓冲数的子数组,滚动时实时更新

Angular 组件实现示例

虚拟滚动组件(virtual-scroller.component.ts)

import { Component, Input, OnInit, ElementRef, ViewChild } from '@angular/core';

@Component({
  selector: 'app-virtual-scroller',
  templateUrl: './virtual-scroller.component.html',
  styleUrls: ['./virtual-scroller.component.css']
})
export class VirtualScrollerComponent implements OnInit {
  @Input() items: any[] = [];
  @Input() itemHeight = 50; // 默认单条高度50px
  @ViewChild('scrollContainer') scrollContainer!: ElementRef<HTMLDivElement>;

  viewportHeight = 0;
  visibleItemCount = 0;
  scrollTop = 0;
  startIndex = 0;
  itemsToDisplay: any[] = [];
  bufferCount = 2; // 前后缓冲项数

  ngOnInit(): void {
    // 初始化视口高度
    this.viewportHeight = this.scrollContainer.nativeElement.clientHeight;
    this.visibleItemCount = Math.ceil(this.viewportHeight / this.itemHeight) + this.bufferCount * 2;
    this.updateDisplayItems();
  }

  onScroll(): void {
    this.scrollTop = this.scrollContainer.nativeElement.scrollTop;
    this.startIndex = Math.floor(this.scrollTop / this.itemHeight);
    // 防止索引越界
    this.startIndex = Math.max(0, Math.min(this.startIndex, this.items.length - this.visibleItemCount));
    this.updateDisplayItems();
  }

  private updateDisplayItems(): void {
    const endIndex = Math.min(this.startIndex + this.visibleItemCount, this.items.length);
    this.itemsToDisplay = this.items.slice(this.startIndex, endIndex);
  }

  // 获取整个列表的总高度,用于占位容器
  get totalListHeight(): string {
    return `${this.items.length * this.itemHeight}px`;
  }

  // 获取当前渲染列表的偏移top值
  get listTopOffset(): string {
    return `${this.startIndex * this.itemHeight}px`;
  }
}

虚拟滚动组件模板(virtual-scroller.component.html)

<div #scrollContainer class="scroll-container" (scroll)="onScroll()">
  <!-- 占位容器:模拟整个列表的高度,让滚动条正常显示 -->
  <div class="list-placeholder" [style.height]="totalListHeight"></div>
  <!-- 实际渲染的列表:绝对定位,随滚动偏移移动 -->
  <div class="displayed-list" [style.top]="listTopOffset">
    <div *ngFor="let item of itemsToDisplay" class="list-item">
      {{ item }}
    </div>
  </div>
</div>

组件样式(virtual-scroller.component.css)

.scroll-container {
  height: 400px; /* 固定视口高度,可通过输入属性动态设置 */
  overflow-y: auto;
  position: relative;
  width: 100%;
}

.list-placeholder {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: -1; /* 占位容器不干扰交互 */
}

.displayed-list {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
}

.list-item {
  height: 50px; /* 与itemHeight保持一致 */
  line-height: 50px;
  border-bottom: 1px solid #eee;
}

Home组件使用示例

<!-- home.component.html -->
<app-virtual-scroller [items]="allData" [itemHeight]="50"></app-virtual-scroller>
// home.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent {
  allData: string[] = [];

  ngOnInit(): void {
    // 模拟1000条数据
    for (let i = 0; i < 1000; i++) {
      this.allData.push(`列表项 ${i + 1}`);
    }
  }
}

关键注意点

  • 必须设置占位容器:没有它的话,滚动容器的高度只会是当前渲染的项的高度,滚动条无法显示整个列表的长度,这就是你说的“之后不会出现滚动条”的核心原因。
  • itemsToDisplay动态更新:每次滚动时重新计算起始索引,截取对应的数据子集,这样只会渲染当前视口附近的项,减少DOM数量。
  • 缓冲项的作用:多渲染前后几个项,避免滚动时出现短暂的空白,提升体验。
  • 固定高度前提:上面的示例是固定高度场景,如果需要支持动态高度,需要额外计算每个项的高度并维护高度映射表,新手先从固定高度入手更易理解。

内容的提问来源于stack exchange,提问作者Gayan Buddhika

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 15:10:33