Angular14中cdkDrag搭配transform scale使用出现异常行为如何解决
Angular 14应用中存在一个通过cdkDrag指令实现拖拽的div元素,同时配置两个按钮控制该div的放大与缩小。
组件内定义默认值为1的scale变量,点击缩放按钮时对scale执行增减0.1的操作,通过ngStyle指令为div绑定样式:[ngStyle]="{ transform: 'scale(' + scale + ')' }",搭配cdkDrag指令实现元素屏幕内拖拽。
由于拖拽、缩放功能均依赖CSS transform属性实现,多次交替执行缩放、拖拽操作(缩放数次后拖拽元素,再次缩放数次后拖拽元素)时,元素会出现异常行为:拖拽过程中元素自动回到初始位置、恢复初始尺寸。
查阅数年前的相关旧bug报告,未找到适配当前场景的有效解决方案。
复现代码
模板文件代码如下:
<div cdkDrag [ngStyle]="{ transform: 'scale(' + scale + ')' }"> <button mat-icon-button (click)="zoomIn()" color="primary"> <mat-icon>zoom_in</mat-icon> </button> <button mat-icon-button (click)="zoomOut()" color="primary"> <mat-icon>zoom_out</mat-icon> </button> <p>Start editing to see some magic happen :)</p> </div>
可通过Stackblitz项目复现该问题。
问题根源
Angular CDK的cdkDrag指令会在拖拽初始化、拖拽进行过程中直接操作元素的transform属性,存储拖拽产生的位移值,和手动绑定的scale变换产生属性覆盖冲突:
- 每次缩放触发
ngStyle更新时,会直接覆盖cdkDrag写入的translate位移值 cdkDrag启动拖拽时会读取元素初始transform值,反过来冲掉动态修改的scale值
修复方式
核心思路是将缩放效果和拖拽位移拆分到不同DOM层,避免两个功能争抢同一个元素的transform属性:
- 外层元素仅绑定
cdkDrag指令,所有拖拽产生的位移作用在这一层,不手动修改该层transform属性 - 内层元素仅负责绑定缩放相关的
ngStyle样式,缩放变换和拖拽层完全隔离
修改后的模板代码:
<!-- 外层仅处理拖拽逻辑 --> <div cdkDrag> <!-- 内层仅处理缩放逻辑 --> <div [ngStyle]="{ transform: 'scale(' + scale + ')', transformOrigin: 'top left' }"> <button mat-icon-button (click)="zoomIn()" color="primary"> <mat-icon>zoom_in</mat-icon> </button> <button mat-icon-button (click)="zoomOut()" color="primary"> <mat-icon>zoom_out</mat-icon> </button> <p>Start editing to see some magic happen :)</p> </div> </div>
注意:必须给内层缩放元素设置transformOrigin: 'top left',否则会出现缩放时元素定位偏移、拖拽手感异常的问题。
如果不希望额外嵌套DOM,也可以通过cdkDrag的cdkDragConstrainPosition输入项手动处理缩放后的坐标换算,同时监听cdkDragEnded事件手动存储拖拽位移,拼接transform时同时写入translate和scale值。但嵌套分层的方案实现成本最低,不会和CDK内部逻辑产生冲突,可适配所有Angular 14+版本。
内容的提问来源于stack exchange,提问作者ufk

