Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular CDK drag-drop with zoom by CSS-property “transform: scale(0.5)” doesn’t work as expected

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

like image 431
pguysor Avatar asked Jun 03 '20 15:06

pguysor


People also ask

How do you drag-and-drop CDK?

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.

Does angular support drag-and-drop?

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 ...

How do I drag-and-drop in angular 6?

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.


3 Answers

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);
}
like image 165
Mrniach Avatar answered Oct 19 '22 18:10

Mrniach


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
      };
like image 35
Jimmy Pannier Avatar answered Oct 19 '22 17:10

Jimmy Pannier


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à!

like image 1
Marco Bérubé Avatar answered Oct 19 '22 18:10

Marco Bérubé