使用 cdkDropList 自由拖动

Free Drag with cdkDropList

我正在做一个项目,我需要实现某种放置区,您可以在其中从列表中拖动元素,然后将它们放到可以自由拖动的区域中。我还想为区域使用 cdkDropList,因为它提供了连接列表的所有工具。 我使用 this example 作为我实施的参考,但我无法使其正常工作。

当我在区域中放置一个项目时,它不会落在光标所在的位置,它只是移动到我的区域的左上角,就像在列表中一样。

当我在区域中拖动一个项目时,它要么正确地拖到我想要它被放下的地方,要么被放到放置点附近,要么只是回到左上角。

这是我的 cdkDrag 元素,它与上面链接的示例不同,因为我绝对需要它在它自己的组件中(我想对其应用一些逻辑未来),但它本质上是相同的概念(cdkDrag div in cdkDropList div)。我设法使用输出将所有需要的事件路由到父元素(区域)。

<div class="element-box"
     cdkDrag
     cdkDragBoundary={{boundary_name}}
     (cdkDragDropped)="dragDroppedEventToParent($event)"
     (cdkDragStarted)="dragStartedEventToParent($event)"
     (cdkDragMoved)="dragMovedEventToParent($event)">
    {{object_name}}
    <div *cdkDragPlaceholder class="field-placeholder"></div>
</div>

这是拖动元素的逻辑

export class ElementBoxComponent implements OnInit {

  @Input () object_name: string;
  @Input () boundary_name: string;
  @Input () itemSelf: any;

  @Output () dragMovedEvent = new EventEmitter<CdkDragMove>();
  @Output () dragStartedEvent = new EventEmitter<CdkDragStart>();
  @Output () dragDroppedEvent = new EventEmitter<any>();

  constructor() {}

  ngOnInit(): void {
  }

  dragMovedEventToParent(event: CdkDragMove) {
    this.dragMovedEvent.emit(event);
  }

  dragStartedEventToParent(event: CdkDragStart){
    this.dragStartedEvent.emit(event);
  }

  dragDroppedEventToParent(event: CdkDragEnd){
    this.dragDroppedEvent.emit({event, "self": this.itemSelf});
  }
}

这是我的 拖放区元素,我在其中渲染拖动元素(您可以看到我将输出路由到我的区域逻辑中的方法):

<div class="drop-container"
      #cdkBoard
      cdkDropList
      [id]="'cdkBoard'"
      [cdkDropListData]="itemsInBoard"
      [cdkDropListConnectedTo]="connectedTo"
      cdkDropListSortingDisabled="true"
      (cdkDropListDropped)="itemDropped($event)">
  <app-element-box *ngFor="let item of itemsInBoard; let i=index" 
                  object_name="{{item.name}}" 
                  boundary_name=".drop-container" 
                  itemSelf="{{item}}"
                  style="position:absolute; z-index:i" 
                  [style.top]="item.top" 
                  [style.left]="item.left"
                  (dragMovedEvent)="elementIsMoving($event)"
                  (dragStartedEvent)="startedDragging($event)"
                  (dragDroppedEvent)="stoppedDragging($event)"></app-element-box>
  
  <svg-container class="bin-container" containerId="bin-image-container" *ngIf="_binVisible" height=40>
    <svg-image class="bin-icon" [imageUrl]="BIN_ICON_URL" height=40 width=40></svg-image>
  </svg-container>
</div>

下面是我的拖放区的 TS 文件中的相关方法:

itemDropped(event: CdkDragDrop<any[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(this.itemsInBoard, event.previousIndex, event.currentIndex);
    } else {
      copyArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex
      )
    }
}

changePosition(event: CdkDragDrop<any>, field) {
    const rectZone = this.dropZone.nativeElement.getBoundingClientRect();
    const rectElement = event.item.element.nativeElement.getBoundingClientRect();
    const top = rectElement.top + event.distance.y - rectZone.top;
    const left = rectElement.left + event.distance.x - rectZone.left;

    const out = (top < 0) || (left < 0) || (top > (rectZone.height - rectElement.height)) || (left > (rectZone.width - rectElement.width));
    if (!out) {
      event.item.element.nativeElement.style.top = top + 'px';
      event.item.element.nativeElement.style.left = left + 'px';
    } else {
      this.itemsInBoard = this.itemsInBoard.filter((x) => x != event.item);
    }
}

同样,唯一的区别是我的元素被封装在它们自己的组件中,并且我访问组件的顶部和左侧样式元素的方式不同(示例中的代码不起作用)。 我知道问题出在我计算 top 和 left 变量的方式上,但我已经坚持了一个星期,似乎无法找出问题所在。

如果您想更好地形象化我在说什么,这里有 short demonstrative video

有谁知道这有什么问题吗?我愿意接受任何建议,谢谢 :)

没有 stackblitz 就很难知道哪里出了问题

this stackblitz我制作了两个ckd-list

一个cdkDropList(todoList)是典型的“列表”,另一个是dropZone(doneList)。我的元素挡路了

{label:string,x:number,y:number,'z-index':number}

dropZone中的cdkDrag挡路了

<div cdkDrag class="item-box" 
        [style.top.px]="item.y" 
        [style.left.px]="item.x" 
        [style.z-index]="item['z-index']"
>

我选择todoList连接到dropZone,但是dropZone没有连接任何东西。当我移动 dropZone 的元素时,如果它移走了这个元素,只需添加到列表

我们需要将 doneList 作为 ElementRef

  @ViewChild('doneList',{read:ElementRef,static:true}) dropZone:ElementRef;

以及必要的功能

  drop(event: CdkDragDrop<any[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex,
      );
      event.item.data.y=(this._pointerPosition.y-this.dropZone.nativeElement.getBoundingClientRect().top)
      event.item.data.x=(this._pointerPosition.x-this.dropZone.nativeElement.getBoundingClientRect().left)
      this.changeZIndex(event.item.data)
    }
    this.posInside={source:null,x:0,y:0}
  }

  moved(event: CdkDragMove) {
    this._pointerPosition=event.pointerPosition;
  }

  changeZIndex(item:any)
  {
    this.done.forEach(x=>x['z-index']=(x==item?1:0))
  }
  changePosition(event:CdkDragDrop<any>,field)
  {
    const rectZone=this.dropZone.nativeElement.getBoundingClientRect()
    const rectElement=event.item.element.nativeElement.getBoundingClientRect()

    let y=+field.y+event.distance.y
    let x=+field.x+event.distance.x
    const out=y<0 || x<0 || (y>(rectZone.height-rectElement.height)) || (x>(rectZone.width-rectElement.width))
    
    if (!out)
    {
       field.y=y
       field.x=x
    }
    else{
      this.todo.push(field)
      this.done=this.done.filter(x=>x!=field)
    }
  }

.html喜欢

<div class="wrapper">
  <div
    cdkDropList
    #todoList="cdkDropList"
    [cdkDropListData]="todo"
    [cdkDropListConnectedTo]="[doneList]"
    class="example-list"
    (cdkDropListDropped)="drop($event)"
  >
    <div
      class="example-box"
      *ngFor="let item of todo"
      cdkDrag
      [cdkDragData]="item"
      (cdkDragMoved)="moved($event)"
    >
      {{ item.label }}
      <div *cdkDragPlaceholder class="field-placeholder"></div>
    </div>
  </div>
  <div
    cdkDropList
    #doneList="cdkDropList"
    [cdkDropListData]="done"
    class="drag-zone"
    cdkDropListSortingDisabled="true"
    (cdkDropListDropped)="drop($event)"
  >
    <ng-container *ngFor="let item of done">
      <div
        cdkDrag
        class="item-box"
        [style.top.px]="item.y"
        [style.left.px]="item.x"
        [style.z-index]="item['z-index']"
        (cdkDragStarted)="changeZIndex(item)"
        (cdkDragDropped)="changePosition($event, item)"
      >
        {{ item.label }}
        <div *cdkDragPlaceholder class="field-placeholder"></div>
      </div>
    </ng-container>
  </div>
</div>