在 Angular 中拖放

我们将介绍@angular/cdk/drag-drop 模块来完成 angular 中的拖放。

我们还将介绍一些在 Angular 中拖放的示例。

在 Angular 中拖放

@angular/cdk/drag-drop 模块为你提供了一种轻松且以声明方式创建拖放界面的方法。该模块支持自由拖动、列表内排序、列表之间传输项目、动画、触摸设备、自定义拖动手柄、预览和占位符。

入门

首先,我们将 DragDropModule 导入 app.module.ts 中的 NgModule

这是 app.module.ts 的代码。

# angular
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { DragDropModule } from "@angular/cdk/drag-drop";
import { AppComponent } from "./app.component";
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule,
CommonModule,
DragDropModule
],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}

现在我们将以下代码导入我们的 app.component.ts

# angular
import { Component, NgModule, ViewChild } from "@angular/core";
import {
CdkDrag,
CdkDragStart,
CdkDropList,
CdkDropListGroup,
CdkDragMove,
CdkDragEnter,
moveItemInArray
} from "@angular/cdk/drag-drop";
import { ViewportRuler } from "@angular/cdk/overlay";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})

现在我们将导出我们的类 AppComponent 并在 app.component.ts 中定义变量

# angular
export class AppComponent {
@ViewChild(CdkDropListGroup) listGroup: CdkDropListGroup<CdkDropList>;
@ViewChild(CdkDropList) placeholder: CdkDropList;
public items: Array<number> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
public target: CdkDropList;
public targetIndex: number;
public source: CdkDropList;
public sourceIndex: number;
public dragIndex: number;
public activeContainer;
constructor(private viewportRuler: ViewportRuler) {
this.target = null;
this.source = null;
}

现在我们将在 app.component.ts 中添加视图初始函数

# angular
ngAfterViewInit() {
let phElement = this.placeholder.element.nativeElement;
phElement.style.display = "none";
phElement.parentElement.removeChild(phElement);
}

现在我们将为 app.component.ts 创建一个 add() 函数以在列表中添加元素。

# angular
add() {
this.items.push(this.items.length + 1);
}

现在我们将创建一个函数来打乱我们在 app.component.ts 中的列表。

#angular
shuffle() {
this.items.sort(function() {
return 0.5 - Math.random();
});
}

现在我们将创建 dragMoved 函数,它拖动 app.component.ts 中的元素。

# angular
dragMoved(e: CdkDragMove) {
let point = this.getPointerPositionOnPage(e.event);
this.listGroup._items.forEach(dropList => {
if (__isInsideDropListClientRect(dropList, point.x, point.y)) {
this.activeContainer = dropList;
return;
}
});
}

现在我们将在 app.component.ts 中创建 dropListDropped 函数,该函数将在释放元素后将其删除。

# angular
dropListDropped() {
if (!this.target) return;
let phElement = this.placeholder.element.nativeElement;
let parent = phElement.parentElement;
phElement.style.display = "none";
parent.removeChild(phElement);
parent.appendChild(phElement);
parent.insertBefore(
this.source.element.nativeElement,
parent.children[this.sourceIndex]
);
this.target = null;
this.source = null;
if (this.sourceIndex != this.targetIndex)
moveItemInArray(this.items, this.sourceIndex, this.targetIndex);
console.log("save here!", this.items);
}

现在我们将获取用户在放置元素之前触摸的页面点。

我们将在 app.component.ts 中添加 getPointerPositionOnPage 函数

# angular
getPointerPositionOnPage(event: MouseEvent | TouchEvent) {
const point = __isTouchEvent(event)
? event.touches[0] || event.changedTouches[0]
: event;
const scrollPosition = this.viewportRuler.getViewportScrollPosition();
return {
x: point.pageX - scrollPosition.left,
y: point.pageY - scrollPosition.top
};
}

现在我们将添加 onClick 函数,该函数将在用户单击列表元素时执行。

# angular
onClick(event) {
console.log(event);
alert("click!");
}

现在我们将以下函数添加到 app.component.ts

function __indexOf(collection, node) {
return Array.prototype.indexOf.call(collection, node);
}
function __isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
return event.type.startsWith("touch");
}
function __isInsideDropListClientRect(
dropList: CdkDropList,
x: number,
y: number
) {
const {
top,
bottom,
left,
right
} = dropList.element.nativeElement.getBoundingClientRect();
return y >= top && y <= bottom && x >= left && x <= right;
}

因此,我们的 app.component.ts 文件将如下所示。

# angular
import { Component, NgModule, ViewChild } from "@angular/core";
import {
CdkDrag,
CdkDragStart,
CdkDropList,
CdkDropListGroup,
CdkDragMove,
CdkDragEnter,
moveItemInArray
} from "@angular/cdk/drag-drop";
import { ViewportRuler } from "@angular/cdk/overlay";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
@ViewChild(CdkDropListGroup) listGroup: CdkDropListGroup<CdkDropList>;
@ViewChild(CdkDropList) placeholder: CdkDropList;
public items: Array<number> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
public target: CdkDropList;
public targetIndex: number;
public source: CdkDropList;
public sourceIndex: number;
public dragIndex: number;
public activeContainer;
constructor(private viewportRuler: ViewportRuler) {
this.target = null;
this.source = null;
}
ngAfterViewInit() {
let phElement = this.placeholder.element.nativeElement;
phElement.style.display = "none";
phElement.parentElement.removeChild(phElement);
}
add() {
this.items.push(this.items.length + 1);
}
shuffle() {
this.items.sort(function() {
return 0.5 - Math.random();
});
}
dragMoved(e: CdkDragMove) {
let point = this.getPointerPositionOnPage(e.event);
this.listGroup._items.forEach(dropList => {
if (__isInsideDropListClientRect(dropList, point.x, point.y)) {
this.activeContainer = dropList;
return;
}
});
}
dropListDropped() {
if (!this.target) return;
let phElement = this.placeholder.element.nativeElement;
let parent = phElement.parentElement;
phElement.style.display = "none";
parent.removeChild(phElement);
parent.appendChild(phElement);
parent.insertBefore(
this.source.element.nativeElement,
parent.children[this.sourceIndex]
);
this.target = null;
this.source = null;
if (this.sourceIndex != this.targetIndex)
moveItemInArray(this.items, this.sourceIndex, this.targetIndex);
console.log("save here!", this.items);
}
dropListEnterPredicate = (drag: CdkDrag, drop: CdkDropList) => {
if (drop == this.placeholder) return true;
if (drop != this.activeContainer) return false;
let phElement = this.placeholder.element.nativeElement;
let sourceElement = drag.dropContainer.element.nativeElement;
let dropElement = drop.element.nativeElement;
let dragIndex = __indexOf(
dropElement.parentElement.children,
this.source ? phElement : sourceElement
);
let dropIndex = __indexOf(dropElement.parentElement.children, dropElement);
if (!this.source) {
this.sourceIndex = dragIndex;
this.source = drag.dropContainer;
phElement.style.width = sourceElement.clientWidth + "px";
phElement.style.height = sourceElement.clientHeight + "px";
sourceElement.parentElement.removeChild(sourceElement);
}
this.targetIndex = dropIndex;
this.target = drop;
phElement.style.display = "";
dropElement.parentElement.insertBefore(
phElement,
dropIndex > dragIndex ? dropElement.nextSibling : dropElement
);
this.placeholder._dropListRef.enter(
drag._dragRef,
drag.element.nativeElement.offsetLeft,
drag.element.nativeElement.offsetTop
);
return false;
};
const point = __isTouchEvent(event)
? event.touches[0] || event.changedTouches[0]
: event;
const scrollPosition = this.viewportRuler.getViewportScrollPosition();
return {
x: point.pageX - scrollPosition.left,
y: point.pageY - scrollPosition.top
};
}
onClick(event) {
console.log(event);
alert("click!");
}
}
function __indexOf(collection, node) {
return Array.prototype.indexOf.call(collection, node);
}
function __isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
return event.type.startsWith("touch");
}
function __isInsideDropListClientRect(
dropList: CdkDropList,
x: number,
y: number
) {
const {
top,
bottom,
left,
right
} = dropList.element.nativeElement.getBoundingClientRect();
return y >= top && y <= bottom && x >= left && x <= right;
}

现在我们将创建一个前端并在 app.component.html 中添加以下代码。

<h1>Drag&Drop with a flex-wrap</h1>
<button (click)="add()">Add</button> <button (click)="shuffle()">Shuffle</button
><br />
<ul class="angular-list">
<li *ngFor="let item of items">{{ item }}</li>
</ul>
<div class="amgular-container" cdkDropListGroup>
<div
cdkDropList
[cdkDropListEnterPredicate]="dropListEnterPredicate"
(cdkDropListDropped)="dropListDropped()"
></div>
<div
cdkDropList
*ngFor="let item of items"
[cdkDropListEnterPredicate]="dropListEnterPredicate"
(cdkDropListDropped)="dropListDropped()"
>
<div
cdkDrag
class="angular-box"
(cdkDragMoved)="dragMoved($event)"
(click)="onClick($event)"
>
{{ item }}
</div>
</div>
</div>

我们将在 app.component.css 中添加我们的样式代码。

# angular
.angular-list {
list-style-type: none;
padding: 0;
}
.angular-list li {
display: table-cell;
padding: 4px;
}
.angular-container {
display: flex;
flex-wrap: wrap;
}
.angular-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
font-size: 30pt;
font-weight: bold;
color: rgba(0, 0, 0, 0.87);
cursor: move;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
background: #fff;
border-radius: 4px;
position: relative;
z-index: 1;
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);
}
.angular-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);
opacity: 0.6;
}
.cdk-drop-list {
display: flex;
padding-right: 15px;
padding-bottom: 15px;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
box-shadow: 0 5px 5px -3px rgba(161, 41, 41, 0.2),
0 8px 10px 1px rgba(141, 58, 58, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
.cdk-drag-placeholder {
opacity: 0.5;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
button {
margin-right: 4px;
}

现在我们的输出将如下所示。

输出:

在 Angular 中拖放