使用 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>
我正在做一个项目,我需要实现某种放置区,您可以在其中从列表中拖动元素,然后将它们放到可以自由拖动的区域中。我还想为区域使用 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>