Angular CSS flexbox 中的 CDK 拖放问题
Angular CDK drag and drop issue inside CSS flexbox
我 运行 使用 Angular CDK 中的拖放模块解决了一个问题。我在容器 div 中使用它,该容器具有(除其他外)以下 CSS 属性:
display: flex;
flex-wrap: wrap;
flex_wrap
属性 在这里,如果包含的可拖动元素不适合容器,它们将换行到第二行,依此类推。
由于拖动是水平的 (cdkDropListOrientation="horizontal"
),所以当所有元素都放在一行中时,这可以正常工作,但一旦它们换行到第二行,拖放就会出现问题。我做了以下 stackblitz 来重现错误:https://stackblitz.com/edit/angular-fytgp6 .
如果有人知道如何解决此问题或考虑解决此问题的方法,那将会很有帮助!
这是 CDK 拖放的已知问题:https://github.com/angular/material2/issues/13372
本质上,您需要有一个定义为 "cdkDropListGroup" 的父级 div,然后您需要将每个可拖动项目视为 "cdkDropList" 除了具有 "cdkDrag"属性就可以了。这应该使得每个项目都是它自己的容器,并且 "cdkDropListGroup" 指令将它们连接在一起。
然后,您可以在 cdkDropList 容器上有一个 *ngFor 来为您的每个数组项生成一个。将 [cdkDropListData]="index" 与 cdkDropList 放在一起,以便您可以将当前拖动的索引传输到 cdkDrag。使用子 cdkDrag 元素,您可以使用 [cdkDragData]="index" 获取此索引。然后,在 cdkDrag 子级上有一个事件绑定 (cdkDragEntered)="entered($event)" ,它会在您每次尝试将元素拖到其中一个新容器时触发。在输入的函数中,使用 CDK 中的 moveItemInArray 方法来传输项目。
entered(event: CdkDragEnter) {
moveItemInArray(this.items, event.item.data, event.container.data);
}
<div style="display:flex;flex-wrap:wrap" cdkDropListGroup>
<div cdkDropList [cdkDropListData]="i" *ngFor="let item of items; let i = index;" [style.width]="item.width || '100%'">
<div cdkDrag [cdkDragData]="i" (cdkDragEntered)="entered($event)">
{{item}}
</div>
</div>
</div>
如果这对您不起作用,那么您可以尝试使用 mat-grid 来控制您的布局。
<mat-grid-list cdkDropListGroup>
<mat-grid-tile cdkDropList [cdkDropListData]="i" *ngFor="let item of items; let i = index;" [colspan]="item.cols" [rowspan]="item.rows">
<div cdkDrag [cdkDragData]="i" (cdkDragEntered)="entered($event)">
{{item}}
</div>
</mat-grid-tile>
</mat-grid-list>
我已经使用 Angular 的工具包(cdkDropListGroup、moveItemInArray 和 transferArrayItem)实现了一个简单的解决方案:https://stackblitz.com/edit/angular-drag-n-drop-mixed-orientation-example
我所做的就是创建一个项目 table 矩阵,用作模板的视图模型,并且组件在项目列表(输入模型)与 table(视图模型)之间同步。我在这里发布了详细的解释:https://taitruong.github.io/software-developer.org/post/2019/10/26/Angular-drag'n'drop-mixed-orientation-in-flex-row-wrap/
模板:
<div #tableElement cdkDropListGroup>
<!-- Based on the width of template reference #tableElement' and item box width,
columns per row can be calculated and a items table matrix is initialized-->
<div
fxLayout="row"
*ngFor="let itemsRow of getItemsTable(tableElement)"
cdkDropList
cdkDropListOrientation="horizontal"
[cdkDropListData]="itemsRow"
(cdkDropListDropped)="reorderDroppedItem($event)"
>
<!-- Component.reorderDroppedItem():
reorders table/view model, update input model, and resize table matrix-->
<div *ngFor="let item of itemsRow" cdkDrag>
<div class="drag-placeholder" *cdkDragPlaceholder></div>
<div fxLayoutAlign="center center" class="item-box">{{ item }}</div>
</div>
</div>
</div>
CSS:
.item-box {
width: 150px;
height: 150px;
border: solid 3px #ccc;
background: #fff;
font-size: 30pt;
font-weight: bold;
border-radius: 5px;
margin: 0px 0px 5px 5px;
}
.drag-placeholder {
background: #ccc;
border: dotted 3px #999;
height: 150px;
width: 50px;
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
组件:
export class AppComponent {
// one dimensional input model
items: Array<number> = Array.from({ length: 21 }, (v, k) => k + 1);
// two dimensional table matrix representing view model
itemsTable: Array<number[]>;
// fix column width as defined in CSS (150px + 5px margin)
boxWidth = 155;
// calculated based on dynamic row width
columnSize: number;
getItemsTable(tableElement: Element): number[][] {
// calculate column size per row
const { width } = tableElement.getBoundingClientRect();
const columnSize = Math.round(width / this.boxWidth);
// view has been resized? => update table with new column size
if (columnSize != this.columnSize) {
this.columnSize = columnSize;
this.initTable();
}
return this.itemsTable;
}
initTable() {
// create table rows based on input list
// example: [1,2,3,4,5,6] => [ [1,2,3], [4,5,6] ]
this.itemsTable = this.items
.filter((_, outerIndex) => outerIndex % this.columnSize == 0) // create outter list of rows
.map((
_,
rowIndex // fill each row from...
) =>
this.items.slice(
rowIndex * this.columnSize, // ... row start and
rowIndex * this.columnSize + this.columnSize // ...row end
)
);
}
reorderDroppedItem(event: CdkDragDrop<number[]>) {
// same row/container? => move item in same row
if (event.previousContainer === event.container) {
moveItemInArray(
event.container.data,
event.previousIndex,
event.currentIndex
);
} else {
// different rows? => transfer item from one to another list
transferArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex
);
}
// update items after drop: flatten matrix into list
// example: [ [1,2,3], [4,5,6] ] => [1,2,3,4,5,6]
this.items = this.itemsTable.reduce(
(previous, current) => previous.concat(current),
[]
);
// re-initialize table - makes sure each row has same numbers of entries
// example: [ [1,2], [3,4,5,6] ] => [ [1,2,3], [4,5,6] ]
this.initTable();
}
}
我 运行 使用 Angular CDK 中的拖放模块解决了一个问题。我在容器 div 中使用它,该容器具有(除其他外)以下 CSS 属性:
display: flex;
flex-wrap: wrap;
flex_wrap
属性 在这里,如果包含的可拖动元素不适合容器,它们将换行到第二行,依此类推。
由于拖动是水平的 (cdkDropListOrientation="horizontal"
),所以当所有元素都放在一行中时,这可以正常工作,但一旦它们换行到第二行,拖放就会出现问题。我做了以下 stackblitz 来重现错误:https://stackblitz.com/edit/angular-fytgp6 .
如果有人知道如何解决此问题或考虑解决此问题的方法,那将会很有帮助!
这是 CDK 拖放的已知问题:https://github.com/angular/material2/issues/13372
本质上,您需要有一个定义为 "cdkDropListGroup" 的父级 div,然后您需要将每个可拖动项目视为 "cdkDropList" 除了具有 "cdkDrag"属性就可以了。这应该使得每个项目都是它自己的容器,并且 "cdkDropListGroup" 指令将它们连接在一起。
然后,您可以在 cdkDropList 容器上有一个 *ngFor 来为您的每个数组项生成一个。将 [cdkDropListData]="index" 与 cdkDropList 放在一起,以便您可以将当前拖动的索引传输到 cdkDrag。使用子 cdkDrag 元素,您可以使用 [cdkDragData]="index" 获取此索引。然后,在 cdkDrag 子级上有一个事件绑定 (cdkDragEntered)="entered($event)" ,它会在您每次尝试将元素拖到其中一个新容器时触发。在输入的函数中,使用 CDK 中的 moveItemInArray 方法来传输项目。
entered(event: CdkDragEnter) {
moveItemInArray(this.items, event.item.data, event.container.data);
}
<div style="display:flex;flex-wrap:wrap" cdkDropListGroup>
<div cdkDropList [cdkDropListData]="i" *ngFor="let item of items; let i = index;" [style.width]="item.width || '100%'">
<div cdkDrag [cdkDragData]="i" (cdkDragEntered)="entered($event)">
{{item}}
</div>
</div>
</div>
如果这对您不起作用,那么您可以尝试使用 mat-grid 来控制您的布局。
<mat-grid-list cdkDropListGroup>
<mat-grid-tile cdkDropList [cdkDropListData]="i" *ngFor="let item of items; let i = index;" [colspan]="item.cols" [rowspan]="item.rows">
<div cdkDrag [cdkDragData]="i" (cdkDragEntered)="entered($event)">
{{item}}
</div>
</mat-grid-tile>
</mat-grid-list>
我已经使用 Angular 的工具包(cdkDropListGroup、moveItemInArray 和 transferArrayItem)实现了一个简单的解决方案:https://stackblitz.com/edit/angular-drag-n-drop-mixed-orientation-example
我所做的就是创建一个项目 table 矩阵,用作模板的视图模型,并且组件在项目列表(输入模型)与 table(视图模型)之间同步。我在这里发布了详细的解释:https://taitruong.github.io/software-developer.org/post/2019/10/26/Angular-drag'n'drop-mixed-orientation-in-flex-row-wrap/
模板:
<div #tableElement cdkDropListGroup>
<!-- Based on the width of template reference #tableElement' and item box width,
columns per row can be calculated and a items table matrix is initialized-->
<div
fxLayout="row"
*ngFor="let itemsRow of getItemsTable(tableElement)"
cdkDropList
cdkDropListOrientation="horizontal"
[cdkDropListData]="itemsRow"
(cdkDropListDropped)="reorderDroppedItem($event)"
>
<!-- Component.reorderDroppedItem():
reorders table/view model, update input model, and resize table matrix-->
<div *ngFor="let item of itemsRow" cdkDrag>
<div class="drag-placeholder" *cdkDragPlaceholder></div>
<div fxLayoutAlign="center center" class="item-box">{{ item }}</div>
</div>
</div>
</div>
CSS:
.item-box {
width: 150px;
height: 150px;
border: solid 3px #ccc;
background: #fff;
font-size: 30pt;
font-weight: bold;
border-radius: 5px;
margin: 0px 0px 5px 5px;
}
.drag-placeholder {
background: #ccc;
border: dotted 3px #999;
height: 150px;
width: 50px;
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
组件:
export class AppComponent {
// one dimensional input model
items: Array<number> = Array.from({ length: 21 }, (v, k) => k + 1);
// two dimensional table matrix representing view model
itemsTable: Array<number[]>;
// fix column width as defined in CSS (150px + 5px margin)
boxWidth = 155;
// calculated based on dynamic row width
columnSize: number;
getItemsTable(tableElement: Element): number[][] {
// calculate column size per row
const { width } = tableElement.getBoundingClientRect();
const columnSize = Math.round(width / this.boxWidth);
// view has been resized? => update table with new column size
if (columnSize != this.columnSize) {
this.columnSize = columnSize;
this.initTable();
}
return this.itemsTable;
}
initTable() {
// create table rows based on input list
// example: [1,2,3,4,5,6] => [ [1,2,3], [4,5,6] ]
this.itemsTable = this.items
.filter((_, outerIndex) => outerIndex % this.columnSize == 0) // create outter list of rows
.map((
_,
rowIndex // fill each row from...
) =>
this.items.slice(
rowIndex * this.columnSize, // ... row start and
rowIndex * this.columnSize + this.columnSize // ...row end
)
);
}
reorderDroppedItem(event: CdkDragDrop<number[]>) {
// same row/container? => move item in same row
if (event.previousContainer === event.container) {
moveItemInArray(
event.container.data,
event.previousIndex,
event.currentIndex
);
} else {
// different rows? => transfer item from one to another list
transferArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex
);
}
// update items after drop: flatten matrix into list
// example: [ [1,2,3], [4,5,6] ] => [1,2,3,4,5,6]
this.items = this.itemsTable.reduce(
(previous, current) => previous.concat(current),
[]
);
// re-initialize table - makes sure each row has same numbers of entries
// example: [ [1,2], [3,4,5,6] ] => [ [1,2,3], [4,5,6] ]
this.initTable();
}
}