Angular 18中CDK Drag&Drop手动调整项顺序(上下按钮)时添加过渡动画的实现方案
Angular 18中CDK Drag&Drop手动调整项顺序(上下按钮)时添加过渡动画的实现方案
我完全懂你现在的困扰——用CDK拖拽调整电影列表时,动画流畅自然,但点击上下按钮手动移动项的时候,元素唰的一下就换位置了,完全没过渡,体验感一下子就下来了。别担心,咱们可以结合Angular的动画能力,给手动移动也配上和拖拽一致的顺滑动画,让两种操作的体验统一起来。
第一步:准备动画基础配置
首先,要确保你的Angular 18独立组件项目支持动画,在main.ts的应用配置里添加动画提供者:
import { bootstrapApplication } from '@angular/platform-browser'; import { provideAnimations } from '@angular/platform-browser/animations'; import { AppComponent } from './app/app.component'; bootstrapApplication(AppComponent, { providers: [provideAnimations()] });
第二步:修改组件逻辑与添加动画触发器
接下来更新你的拖拽组件,导入动画相关模块,定义列表项的动画触发器,同时在手动移动的方法里触发和拖拽一致的动画效果:
import {Component} from '@angular/core'; import { CdkDrag, CdkDragDrop, CdkDragPlaceholder, CdkDropList, moveItemInArray, } from '@angular/cdk/drag-drop'; import { trigger, transition, style, animate, query } from '@angular/animations'; // 定义列表变化时的动画触发器,监听DOM重排过程 const listItemAnimation = trigger('listItemAnimation', [ transition('* <=> *', [ query(':not(:enter, :leave)', [ style({ transform: 'translateY(0)' }), animate('300ms cubic-bezier(0.35, 0, 0.25, 1)', style({ transform: 'translateY(0)' })) ], { optional: true }) ]) ]); @Component({ standalone: true, selector: 'cdk-drag-drop-custom-placeholder-example', template: `<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)" @listItemAnimation> @for (movie of movies; track movie; let i = $index) { <button (click)="moveUp(movie)">Up</button> <button (click)="moveDown(movie)">Down</button> <div class="example-box" cdkDrag [attr.data-movie]="movie"> <div class="example-custom-placeholder" *cdkDragPlaceholder></div> {{movie}} </div> } </div>`, styleUrl: 'cdk-drag-drop-custom-placeholder-example.css', imports: [CdkDropList, CdkDrag, CdkDragPlaceholder], animations: [listItemAnimation] // 注册动画到组件 }) export class CdkDragDropCustomPlaceholderExampleComponent { movies = [ 'Episode I - The Phantom Menace', 'Episode II - Attack of the Clones', 'Episode III - Revenge of the Sith', 'Episode IV - A New Hope', 'Episode V - The Empire Strikes Back', ]; moveUp(item: string) { const index = this.movies.indexOf(item); if (index === 0) return; // 已经是第一个,无需操作 // 通过自定义属性定位到目标元素 const element = document.querySelector(`.example-box[data-movie="${item}"]`); if (element) { // 添加拖拽动画类,模拟拖拽时的视觉效果 element.classList.add('cdk-drag-dragging'); } // 调整数组顺序 moveItemInArray(this.movies, index, index - 1); // 动画结束后移除类,恢复正常状态 setTimeout(() => { element?.classList.remove('cdk-drag-dragging'); }, 300); // 时长和CSS过渡保持一致 } moveDown(item: string) { const index = this.movies.indexOf(item); if (index === this.movies.length - 1) return; // 已经是最后一个,无需操作 const element = document.querySelector(`.example-box[data-movie="${item}"]`); if (element) { element.classList.add('cdk-drag-dragging'); } moveItemInArray(this.movies, index, index + 1); setTimeout(() => { element?.classList.remove('cdk-drag-dragging'); }, 300); } drop(event: CdkDragDrop<string[]>) { moveItemInArray(this.movies, event.previousIndex, event.currentIndex); } }
第三步:增强CSS过渡效果
最后修改CSS文件,让动画效果更明显,和拖拽操作的视觉反馈保持统一:
.example-list { width: 500px; max-width: 100%; border: solid 1px #ccc; min-height: 60px; display: block; background: white; border-radius: 4px; overflow: hidden; } .example-box { padding: 20px 10px; border-bottom: solid 1px #ccc; color: rgba(0, 0, 0, 0.87); display: flex; align-items: center; justify-content: space-between; box-sizing: border-box; cursor: move; background: white; font-size: 14px; /* 给列表项添加基础位置过渡 */ transition: transform 0.3s cubic-bezier(0.35, 0, 0.25, 1); } .example-box:last-child { border: none; } .example-custom-placeholder { background: #ccc; border: dotted 3px #999; min-height: 60px; transition: transform 0.3s ease; } /* 手动移动时的动画效果,和拖拽状态保持一致 */ .cdk-drag-dragging { transform: scale(1.05); box-shadow: 0 5px 15px rgba(0,0,0,0.2); transition: all 0.3s cubic-bezier(0.35, 0, 0.25, 1); }
这样修改后,不管是拖拽还是点击上下按钮,列表项的移动都会有流畅的缩放+位置过渡动画,两种操作的体验就完全统一啦!
备注:内容来源于stack exchange,提问作者Naveh Elya
相关产品推荐
相关产品推荐

