angularangular-materialdrag-and-droprotatetransformangular-cdk-drag-drop

Lost Rotate position on Drag element with Angular CDK DRAG


I'm trying to rotate an element with a range slider, but every time I drag the element it loses the value of the rotation while dragging and it also changes the position of the box and places it at the start. I am attaching a link to a simple test project that I have created in stackblitz, so that I can recreate the problem that I have.

stackblitz drag and rotate example

Could someone guide me in the solution?

I put the code here in case someone does not work well the link to the test project:

app.module.ts

import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";

import { DragDropModule } from "@angular/cdk/drag-drop";

import { AppComponent } from "./app.component";
import { HelloComponent } from "./hello.component";

@NgModule({
  imports: [BrowserModule, FormsModule, DragDropModule],
  declarations: [AppComponent, HelloComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

app.component.ts

import { Component, Renderer2 } from "@angular/core";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  rotateValue = 0;
  dragPosition = { x: 0, y: 0 };

  constructor(private renderer: Renderer2) {}

  setRotate(value: string) {
    this.rotateValue = Number(value);
    this.renderer.setStyle(
      document.querySelector(".example-box"),
      "transform",
      `rotate(${this.rotateValue}deg)`
    );
  }
}

app.component.html

<h1 class="text-center m-3">Drag And Drop Project</h1>
<hr>

<div class="row m-5">

    <div class="col-sm-7">
        <div class="example-boundary">
            <div class="example-box" cdkDragBoundary=".example-boundary" cdkDrag>
                I can only be dragged within the dotted container
            </div>
        </div>
    </div>

    <div class="col-sm-5">
        <h4>SETTINGS</h4>
        <ul class="list-group mb-3">
            <li class="list-group-item d-flex justify-content-between lh-condensed">
                <div>
                    <h6 class="my-0">Rotate the box</h6>
                    <input #rotation
                   type="range"
                   class="custom-range  my-2"
                    min="-150" max="150"
                    [(ngModel)]="rotateValue"
                   [value]='rotateValue'
                   (change)="setRotate(rotation.value)"
                  >
        </div>
                    <span id="grados" class="text-muted">{{rotateValue}}º</span>
            </li>

        </ul>
    </div>
</div>

app.component.css

.example-box {
  width: 140px;
  height: 140px;
  border: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  cursor: move;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  background: #fff;
  border-radius: 4px;
  margin-right: 25px;
  position: relative;
  z-index: 1;
  box-sizing: border-box;
  padding: 10px;
  transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
  box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),
    0 1px 5px 0 rgba(0, 0, 0, 0.12);
}

.example-box:active {
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.example-boundary {
  width: 300px;
  height: 500px;
  max-width: 100%;
  border: dotted #ccc 2px;
}

Thank you very much in advance


Solution

  • The problem you have is that the Cdk Drag is implemented using a CSS transform rule, exactly like your custom rotation. So the two are basically incompatible, when applied to the exact same HTML element. The last operation, either drag or rotate, overwrites the other.

    IMO the easiest workaround is to wrap the element that rotates inside a draggable wrapper.

    Here the updated StackBlitz: https://stackblitz.com/edit/angular-ivy-ynzavj

    The recap of the edits:

    In the template, I wrap the rotatable div with a draggable (I also use [ngStyle] and avoid altogether the direct DOM manipulation, which wasn't by itself a problem, but was needless):

    <div class="example-boundary">
      <div class="box-draggable-wrapper" cdkDragBoundary=".example-boundary" cdkDrag>
        <div class="example-box" [ngStyle]="{'transform':'rotate(' + rotateValue + 'deg)'}" >
          I can only be dragged within the dotted container
        </div>
      </div>
    </div>
    

    Just a bit of CSS for the box-draggable-wrapper:

    .box-draggable-wrapper {
      width: 140px;
      height: 140px;
      display: block;
      border: none;
    }
    

    The component gets cleared up:

    import { Component } from "@angular/core";
    
    @Component({
      selector: "my-app",
      templateUrl: "./app.component.html",
      styleUrls: ["./app.component.css"]
    })
    export class AppComponent {
      rotateValue = 0;
      dragPosition = { x: 0, y: 0 };
    
      constructor() {}
    
      setRotate(value: string) {
        this.rotateValue = +value;
      }
    }