Angular CDK drag-drop with zoom by CSS-property “transform: scale(0.5)” doesn’t work as expected.
If the outer-DIV is scaled by CSS-property “transform: scale(0.5)” the drag doesn’t align properly with the mouse pointer. This happens as soon as the scale is unequal to 1.
Here an example: https://stackblitz.com/edit/angular-2q1mte
I’m aware of this post Drag and drop with pinch zoom doesn't work as expected and therefore the
“@Input('cdkDragConstrainPosition') constrainPosition: (point: Point, dragRef: DragRef) => Point”.
But how to write the custom logic to map the drag properly with the pointer? Or is there any other solution to provide the zoom functionality but keep the drag aligned properly with the mouse pointer?
Any help appreciated
The DragDropModule is imported from '@angular/cdk/drag-drop' and the module is added to import array as shown above. We have added class = ”divlayout” and the details of the class are in app. component.
The @angular/cdk/drag-drop module provides you with a way to easily and declaratively create drag-and-drop interfaces, with support for free dragging, sorting within a list, transferring items between lists, animations, touch devices, custom drag handles, previews, and placeholders, in addition to horizontal lists and ...
To implement drag and drop list you have to include your ngFor repeated section inside a parent div to make that section draggable. This is just a basic structure of how the draggable section should look. Now we have to provide DndList directives to this structure. Basic functioning HTML for drag and drop list.
I've managed to get it working in Angular 12 without using calc()
but keeping the transorm: scale(x)
:
Template:
<div cdkDrag (cdkDragStarted)="startDragging($event)" [cdkDragFreeDragPosition]="dragPosition" [cdkDragConstrainPosition]="dragConstrainPoint">
Component:
_dragPosition: { x: number, y: number } = {x: 0, y: 0};
get dragPosition(): { x: number, y: number } {
return this._dragPosition;
}
dragConstrainPoint = (point: any, dragRef: DragRef) => {
let zoomMoveXDifference = 0;
let zoomMoveYDifference = 0;
if (this.scale != 1) {
zoomMoveXDifference = (1 - this.scale) * dragRef.getFreeDragPosition().x;
zoomMoveYDifference = (1 - this.scale) * dragRef.getFreeDragPosition().y;
}
return {
x: point.x + zoomMoveXDifference as number,
y: point.y + zoomMoveYDifference as number
};
};
startDragging(event: CdkDragStart) {
const position = {
x: this.dragPosition.x * this.scale,
y: this.dragPosition.y * this.scale
};
event.source._dragRef.setFreeDragPosition(position);
(event.source._dragRef as any)._activeTransform = this.dragPosition;
(event.source._dragRef as any)._applyRootElementTransform(this.dragPosition.x, this.dragPosition.y);
}
i got the problem too.
Here the solution : initialize a scale variable and use it the dragConstrainPoint
// change position of object while zooming ! (cdk fix)
dragConstrainPoint = (point, dragRef) => {
let zoomMoveXDifference = 0;
let zoomMoveYDifference = 0;
if (this._scale !- 1) {
zoomMoveXDifference = (1 - this._scale) * dragRef.getFreeDragPosition().x;
zoomMoveYDifference = (1 - this._scale) * dragRef.getFreeDragPosition().y;
}
return {
x: point.x + zoomMoveXDifference ,
y: point.y + zoomMoveYDifference
};
I had the same issue with scaling a full scene with drag and drop using CDK material with Angular 11.
The dragConstrainPoint solution did not work for my context. The idea is really to avoid transform scale or zoom, has it mess with the mouse and objects positions, making it a nightmare with multiplayer drag & drop.
My solution works well in multiplayer having different scene scales
Solution:
I have used CSS variable --scale
on a wrapper div, with dynamic scale calculation
<div class="game-wrapper" style="{{'--scale:'+ gameScale}}">
In Angular the scene scale is calculated like this:
...
private _unsubscriber$: Subject<any> = new Subject();
public screenWidth$: BehaviorSubject<number> = new BehaviorSubject(null);
public screenHeight$: BehaviorSubject<number> = new BehaviorSubject(null);
public gameScale: number = 1;
public constructor(
@Inject(DOCUMENT) private document: any
) { }
...
ngOnInit(): void {
//Manage screen width
this._setScreenResolution(window.innerWidth, window.innerHeight);
fromEvent(window, 'resize')
.pipe(
debounceTime(1000),
takeUntil(this._unsubscriber$)
).subscribe((evt: any) => {
this._setScreenResolution(evt.target.innerWidth, evt.target.innerHeight);
});
}
ngOnDestroy() {
this._unsubscriber$.next();
this._unsubscriber$.complete();
}
private _setScreenResolution(width: number, height: number): void {
this.screenWidth$.next(width);
this.screenHeight$.next(height);
let scaleX = windowWidth / this.sceneWidth;
let scaleY = windowHeight / this.sceneHeight;
let scale = scaleX;
if(scaleX > scaleY) {
scale = scaleY;
}
//console.log("getGameScale", scale.toFixed(3));
scale = parseFloat(scale.toFixed(3));
this.gameScale = scale;
}
In the HTML templates and CSS, at each time pixels are involved, we add the calc().
Here some exemples of CSS with those calculs
width: calc(18px * var(--scale));
padding: calc(1px * var(-scale)) calc(6px * var(-scale));
background-size: calc(30px * var(-scale)) calc(30px * var(-scale)), cover;
margin: calc(20px * var(-scale)) calc(30px * var(-scale));
bottom: calc(300px * var(--scale));
border-radius: calc(4px * var(--scale));
font-size: calc(21px * var(--scale));
left: calc(10px * var(--scale));
top: calc(4px * var(--scale));
box-shadow: 0 0 0 calc(4px * var(--scale)) rgba(0,0,0,0.1) inset;
border: calc(2px * var(--scale)) solid #6898f3;
height: calc(32px * var(--scale));
min-height: calc(26px * var(--scale));
line-height: calc(42px * var(--scale));
For the local CDK drag move and drag end values to be well sent to other multiplayers, you need to scale them by dividing by the scale:
dragMoved(event: CdkDragMove) {
let {offsetLeft, offsetTop} = event.source.element.nativeElement;
let {x, y} = event.distance;
x = x / this.gameScale;
y = y / this.gameScale;
offsetLeft = offsetLeft / this.gameScale;
offsetTop = offsetTop / this.gameScale;
//Remove scene offset from calcul (ex. for aside left menu)
let xpos = ((offsetLeft + x) - this.offset.x);
let ypos = ((offsetTop + y) - this.offset.y);
let position: any = {x: xpos, y: ypos};
//The position is then sent to a multiplayer management relay message...
}
//same for drag end + do not forget to reset the transform 3D at the end of the function
dragEnded(event: CdkDragEnd) {
...
//The position at drag end is set on the object and for all other players
//Reset translate3D
//event.source._dragRef.setFreeDragPosition({x: 0, y: 0});
event.source._dragRef.reset();
}
Voilà!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With