Angular Material拖拽:如何跨独立容器同步拖拽动画?
跨容器同步Angular Material拖拽动画的实现方案
需求场景
使用Angular Material CDK的cdkDropList和cdkDrag实现拖拽功能时,需要让两个独立容器的对应项在拖拽过程中同步动画:第一个列表的项拖拽时,第二个列表的对应项实时镜像其拖拽位置,而非仅在拖拽结束后同步数据。
当前实现局限
现有代码仅能在拖拽完成后通过moveItemInArray同步两个列表的数据,但拖拽过程中第二个列表的元素没有任何动画反馈,体验不佳。
优雅实现思路
通过监听CDK Drag的拖拽生命周期事件,实时同步元素的位置偏移量,为第二个列表的对应元素添加动态样式实现镜像动画。
具体实现步骤
1. 绑定拖拽事件到第一个列表元素
为第一个列表的cdkDrag元素添加cdkDragStarted、cdkDragMoved、cdkDragEnded事件,用于跟踪拖拽状态和实时位置。
2. 维护拖拽状态数组
定义dragState数组,存储每个索引对应的拖拽状态(是否正在拖拽、初始位置、实时偏移量),确保两个列表的对应项能准确关联。
3. 动态绑定样式实现镜像动画
为第二个列表的元素绑定transform和opacity样式,根据dragState实时更新位置和视觉反馈。
修改后的完整代码
HTML模板
<div cdkDropList cdkDropListOrientation="horizontal" class="parent" (cdkDropListDropped)="drop($event)" > @for(data of data1; track data; let i = $index){ <span class="child" cdkDragLockAxis="x" cdkDragPreviewContainer="parent" cdkDrag (cdkDragStarted)="onDragStarted(i)" (cdkDragMoved)="onDragMoved($event, i)" (cdkDragEnded)="onDragEnded(i)" > {{ data }} </span> } </div> <div class="parent"> @for(data of data2; track data; let i = $index){ <span class="child" [style.transform]="dragState[i] ? `translateX(${dragState[i].offsetX}px)` : ''" [style.opacity]="dragState[i]?.isDragging ? 0.5 : 1" > {{ data }} </span> } </div>
TypeScript组件
import { Component } from '@angular/core'; import { CdkDragDrop, CdkDragMove, moveItemInArray } from '@angular/cdk/drag-drop'; interface DragState { isDragging: boolean; initialX: number; offsetX: number; } @Component({ selector: 'app-dummy', templateUrl: './dummy.component.html', styleUrls: ['./dummy.component.scss'] }) export class DummyComponent { protected readonly data1 = [1, 2, 3, 4]; protected readonly data2 = ['A', 'B', 'C', 'D']; dragState: (DragState | null)[] = Array(this.data1.length).fill(null); drop($event: CdkDragDrop<unknown[]>): void { moveItemInArray(this.data1, $event.previousIndex, $event.currentIndex); moveItemInArray(this.data2, $event.previousIndex, $event.currentIndex); // 拖拽完成后重置所有状态 this.dragState = Array(this.data1.length).fill(null); } onDragStarted(index: number): void { this.dragState[index] = { isDragging: true, initialX: 0, offsetX: 0 }; } onDragMoved(event: CdkDragMove, index: number): void { if (!this.dragState[index]) return; // 初始化初始位置,避免首次计算偏移错误 if (this.dragState[index].initialX === 0) { this.dragState[index].initialX = event.pointerPosition.x - event.source.getFreeDragPosition().x; } // 计算实时水平偏移量 this.dragState[index].offsetX = event.pointerPosition.x - this.dragState[index].initialX; } onDragEnded(index: number): void { if (this.dragState[index]) { this.dragState[index].isDragging = false; // 延迟清除状态,避免与CDK的结束动画冲突 setTimeout(() => { this.dragState[index] = null; }, 100); } } }
优化说明
- 利用
dragState数组精准关联两个列表的对应项,确保动画同步的准确性 - 通过
translateX实现平滑的位置同步,配合opacity调整增强拖拽时的视觉区分度 - 拖拽结束后延迟清除状态,避免与CDK内置的拖拽结束动画产生冲突,保证过渡自然
内容的提问来源于stack exchange,提问作者Verim
相关产品推荐
相关产品推荐

