在 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;
}
現在我們的輸出將如下所示。
輸出:
Rana is a computer science graduate passionate about helping people to build and diagnose scalable web application problems and problems developers face across the full-stack.
LinkedIn