Angular cdk-virtual-scroll-viewport滚轮向上滚动失效卡顿
Angular CDK虚拟滚动上滚卡顿/失效问题排查解决
问题现象
- 基于Angular + Angular Material + CDK虚拟滚动实现包含5000个元素的下拉选择组件,鼠标滚轮向上滚动时元素停止响应、完全无法滚动,预期行为为滚动过程流畅无卡顿
- 调整CSS将
mat-option宽度设为100px后,容器右侧空白区域滚动表现符合预期,但鼠标悬浮在mat-option区域滚动时会出现卡顿 - 复现Demo:Angular虚拟滚动问题复现
- 问题效果截图:

核心实现代码
组件模板代码:
<mat-form-field> <mat-select [formControl]="form" placeholder="State" class="virtual-scroll" (openedChange)="scrollViewport.scrollToIndex(states.indexOf(form?.value || 0))" > <cdk-virtual-scroll-viewport [itemSize]="48" [style.height.px]="6*48" minBufferPx="288" maxBufferPx="288" #scrollViewport > <mat-option *cdkVirtualFor="let state of states" [value]="state"> {{state}} </mat-option> </cdk-virtual-scroll-viewport> </mat-select> </mat-form-field>
调整的自定义CSS代码:
.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper { min-width: unset !important; } mat-option { width: 100px !important; background-color: red; }
问题根因
滚动事件冲突是核心诱因:
MatSelect组件自身默认实现了面板滚动逻辑,会监听作用在mat-option上的滚轮事件做内置滚动处理,和cdk-virtual-scroll-viewport的滚动事件监听形成冲突- 当鼠标悬浮在
mat-option元素上触发滚轮时,事件优先被MatSelect的事件处理器拦截,没有传递到虚拟滚动容器上,导致虚拟滚动无法接收到滚动指令,出现卡顿、锁死;鼠标在右侧空白区域(不在mat-option范围内)时,滚轮事件直接作用在虚拟滚动视口上,因此滚动正常 - 配置的缓冲区大小和视口高度完全相等,向上滚动时虚拟滚动渲染新缓冲区元素的过程中,鼠标悬浮的选项刚好被DOM替换,导致事件目标丢失,进一步放大了卡顿、滚动失效的问题
解决方案
- 方案1:剥离MatSelect默认滚动能力,将滚动权限完全交给CDK虚拟滚动
首先给mat-select添加[disableOptionCentering]="true"属性关闭默认的选项居中滚动逻辑,再添加以下CSS覆盖默认样式,禁用MatSelect面板自身的滚动:
确保所有滚轮事件都直接作用在虚拟滚动视口容器上,从根源避免双滚动容器事件冲突。.mat-select-panel { overflow: hidden !important; } - 方案2:捕获阶段拦截滚轮事件,手动触发虚拟滚动
在组件逻辑中获取虚拟滚动视口实例,在事件捕获阶段绑定滚轮事件,阻止事件冒泡到MatSelect的处理器,同时手动计算滚动偏移量触发滚动:import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { ViewChild } from '@angular/core'; // 组件内获取视口实例 @ViewChild('scrollViewport') scrollViewport: CdkVirtualScrollViewport; ngAfterViewInit() { const viewportEl = this.scrollViewport.elementRef.nativeElement; // 捕获阶段绑定事件,优先触发 viewportEl.addEventListener('wheel', (e: WheelEvent) => { e.stopPropagation(); const currentOffset = this.scrollViewport.measureScrollOffset(); this.scrollViewport.scrollToOffset(currentOffset + e.deltaY); }, true); } - 方案3:调整虚拟滚动缓冲区配置
不要将minBufferPx和maxBufferPx设置为和视口高度完全相等的值,适当放大缓冲区减少滚动过程中的DOM替换频率,推荐配置:<cdk-virtual-scroll-viewport [itemSize]="48" [style.height.px]="6*48" minBufferPx="480" maxBufferPx="960" #scrollViewport >
内容的提问来源于stack exchange,提问作者shutsman
相关产品推荐
相关产品推荐

