Angular CDK Drag and Drop 资源日历拖拽drop事件卡顿性能优化求助
Angular CDK 拖拽资源日历卡顿优化方案
核心问题根因
- 放置区域滥用
cdkDropList:每个司机每小时的单元格都单独声明了cdkDropList,假设你有20个司机+24小时列,就会生成480个独立放置区域,CDK拖拽过程中需要遍历所有区域计算碰撞,直接导致拖拽过程卡顿 - 模板中频繁执行函数:
ngStyle和*ngIf中直接调用hoursToMs方法,Angular每次变更检测都会重复执行该函数,拖拽过程中变更检测触发频率极高,直接放大性能开销 - 放置后全量重绘:drop事件接口请求成功后调用
initDay重新拉取全量日历数据,所有DOM节点会销毁重建,导致放置后响应卡顿 - 重复DOM元素冗余:每个货件都内嵌了完整的
mat-menu实例,大量重复菜单节点占用内存,拖慢渲染速度 - 未设置cdkDropList关联:所有
cdkDropList没有设置[cdkDropListConnectedTo]属性,CDK会默认和所有同层级放置区域关联,进一步增加碰撞计算量 - 原生拖拽属性冲突:模板中多处冗余添加了原生
draggable="true"属性,和CDK拖拽逻辑冲突,额外触发不必要的事件
具体优化措施
1. 重构放置区域,减少cdkDropList数量
不要给每个小时单元格单独加cdkDropList,改为每个司机列只声明一个cdkDropList,放置时通过鼠标位置计算当前对应的小时,修改后HTML结构示例:
<div *ngFor="let driver of drivers; trackBy: trackByDriver" class="driver-column"> <div class="column-info" [matTooltip]="driver.driver"> {{ driver.driverName | slice: 0:21 }} </div> <!-- 整个司机列作为一个放置区域 --> <div class="working-hours-droparea" cdkDropList id="shipmentCell" [cdkDropListConnectedTo]="['containersQueList', 'chassisQueList', 'shipmentsQueList', 'prepullsQueList', 'stagingsQueList']" (cdkDropListDropped)="dropIntoShipmentsTable($event, driver)" > <div *ngFor="let hour of workingHours; trackBy: trackByHour" class="hour-droparea" [ngStyle]="hour >= driver.startTimeMs && hour <= driver.endTimeMs ? {background: 'RGBA(0,114,0,0.37)'} : {}" > <ng-container *ngFor="let shipment of driver.items; trackBy: trackByShipment"> <span (contextmenu)="onRightClick($event, shipment, driver)" *ngIf="hour === shipment.timeFromMs" [cdkDragData]="shipment" [cdkDragDisabled]="shipment.isReturn" [ngClass]="'shipment-duration-' + shipment.duration" [style.background]="shipment.colorCode" cdkDrag > <small>{{ shipment.originDestination }}</small> <br /> <small class="container"><strong>{{ shipment.container }}</strong></small> <br /> <small>{{ shipment.chassisPool }}</small> </span> </ng-container> </div> </div> </div> <!-- 全局只保留一个右键菜单 --> <div #rightClickMenuTrigger [matMenuTriggerFor]="shipmentRightClick" [style.left]="menuTopLeftPosition.x" [style.top]="menuTopLeftPosition.y" style="visibility: hidden; position: fixed" ></div> <mat-menu #shipmentRightClick="matMenu"> <ng-template let-item="item" matMenuContent> <!-- 原有菜单项内容 --> </ng-template> </mat-menu>
注意提前把
driver.startTime、driver.endTime转为毫秒存在startTimeMs、endTimeMs属性,把shipment.timeFrom转为毫秒存在shipment.timeFromMs属性,不要在模板中调用转换函数
2. 放置时计算对应小时
在dropIntoShipmentsTable函数中通过拖拽事件的Y坐标,计算当前落在哪个小时区间,同时添加请求锁避免重复触发:
// 新增请求锁变量 isProcessingDrop = false; dropIntoShipmentsTable(event: CdkDragDrop<any>, driver) { if (this.isProcessingDrop || event.item.data == null) return; this.isProcessingDrop = true; // 计算放置位置对应的小时 const dropAreaRect = event.container.element.nativeElement.getBoundingClientRect(); const offsetY = event.dropPoint.y - dropAreaRect.top; // 每个小时单元格高度,根据你实际的CSS调整 const hourHeight = 60; const hourIndex = Math.max(0, Math.min(this.workingHours.length - 1, Math.floor(offsetY / hourHeight))); const targetHour = this.workingHours[hourIndex]; // 原有updateDriver逻辑,hour替换为targetHour let updateDriver; if (event.previousContainer.id === "shipmentCell") { updateDriver = { DriverId: driver, id: event.item.data.id, TimeFrom: this.moment(targetHour).format("MM/DD/YYYY HH:mm"), userId: this.user.id().toString(), }; } else { updateDriver = { DriverId: driver, containerId: event.previousContainer.id === "containersQueList" ? event.item.data.id : null, chassisId: event.previousContainer.id === "chassisQueList" ? event.item.data.id : null, id: ["shipmentsQueList", "prepullsQueList", "stagingsQueList"].includes(event.previousContainer.id) ? event.item.data.id : null, TimeFrom: this.moment(targetHour).format("MM/DD/YYYY HH:mm"), userId: this.user.id().toString(), isContainer: event.item.data.isContainer, }; } this.subs.sink = this.api.assignShipmentToDriver(updateDriver).subscribe(() => { // 增量更新本地数据,不要调用initDay全量刷新 // 1. 把货件从原位置删除 // 2. 把货件添加到目标司机的targetHour对应位置 this.isProcessingDrop = false; }, () => { this.isProcessingDrop = false; }); }
3. 补充trackBy函数减少DOM重建
给货件的循环添加trackByShipment函数:
trackByShipment(index: number, shipment: any) { return shipment.id; }
内容的提问来源于stack exchange,提问作者Jacob
相关产品推荐
相关产品推荐

