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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 16:01:38