本頁說明拖放指令,它們能讓你快速建立具備下列功能的拖放介面:
- 自由拖曳
- 建立一份可重新排序的可拖曳元素清單
- 在清單之間移轉可拖曳元素
- 拖曳動畫
- 沿著某個軸或元素鎖定可拖曳元素
- 加入自訂拖曳控點
- 在拖曳時加入預覽
- 加入自訂拖曳佔位元素
完整的 API 參考請參閱 Angular CDK 的拖放 API 參考頁面。
Component Dev Kit (CDK) 是一組用來建立元件的行為基礎元件。若要使用拖放指令,請先從 npm 安裝 @angular/cdk。你可以在終端機中使用 Angular CLI 完成:
若要使用拖放功能,請在元件中從這些指令匯入你需要的東西。
import {Component} from '@angular/core';
import {CdkDrag} from '@angular/cdk/drag-drop';
@Component({
selector: 'drag-drop-example',
templateUrl: 'drag-drop-example.html',
imports: [CdkDrag],
})
export class DragDropExample {}
加入 cdkDrag 指令,就能讓任何元素變得可拖曳。依預設,所有可拖曳元素都支援自由拖曳。
<div class="example-box" cdkDrag>Drag me around</div>
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Basic Drag&Drop
*/
@Component({
selector: 'cdk-drag-drop-overview-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag],
})
export class CdkDragDropOverviewExample {}
.example-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
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;
font-family: sans-serif;
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);
}
.example-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);
}
把 cdkDropList 指令加到父元素上,就能把可拖曳元素分組為可重新排序的集合。這定義了可拖曳元素可以放置的位置。當元素移動時,放置清單群組中的可拖曳元素會自動重新排列。
拖放指令不會更新你的資料模型。若要更新資料模型,請監聽 cdkDropListDropped 事件(在使用者完成拖曳時),並手動更新資料模型。
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
@for (movie of movies; track movie) {
<div class="example-box" cdkDrag>{{ movie }}</div>
}
</div>
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop sorting
*/
@Component({
selector: 'cdk-drag-drop-sorting-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropSortingExample {
movies = [
'Episode I - The Phantom Menace',
'Episode II - Attack of the Clones',
'Episode III - Revenge of the Sith',
'Episode IV - A New Hope',
'Episode V - The Empire Strikes Back',
'Episode VI - Return of the Jedi',
'Episode VII - The Force Awakens',
'Episode VIII - The Last Jedi',
'Episode IX - The Rise of Skywalker',
];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
}
}
.example-list {
width: 500px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: block;
background: white;
border-radius: 4px;
overflow: hidden;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
border: none;
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
你可以使用 CDK_DROP_LIST 注入權杖來參照 cdkDropList 的執行個體。如需更多資訊,請參閱依賴注入指南與放置清單注入權杖 API。
cdkDropList 指令支援在已連接的放置清單之間移轉可拖曳元素。把一個或多個 cdkDropList 執行個體連接在一起有兩種方式:
cdkDropListConnectedTo 指令既可以搭配對另一個 cdkDropList 的直接參照,也可以參照另一個放置容器的 id。
<!-- This is valid -->
<div cdkDropList #listOne="cdkDropList" [cdkDropListConnectedTo]="[listTwo]"></div>
<div cdkDropList #listTwo="cdkDropList" [cdkDropListConnectedTo]="[listOne]"></div>
<!-- This is valid as well -->
<div cdkDropList id="list-one" [cdkDropListConnectedTo]="['list-two']"></div>
<div cdkDropList id="list-two" [cdkDropListConnectedTo]="['list-one']"></div>
<div class="example-container">
<h2>To do</h2>
<div
cdkDropList
#todoList="cdkDropList"
[cdkDropListData]="todo"
[cdkDropListConnectedTo]="[doneList]"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (item of todo; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
</div>
<div class="example-container">
<h2>Done</h2>
<div
cdkDropList
#doneList="cdkDropList"
[cdkDropListData]="done"
[cdkDropListConnectedTo]="[todoList]"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (item of done; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDropList,
moveItemInArray,
transferArrayItem,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop connected sorting
*/
@Component({
selector: 'cdk-drag-drop-connected-sorting-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropConnectedSortingExample {
todo = ['Get to work', 'Pick up groceries', 'Go home', 'Fall asleep'];
done = ['Get up', 'Brush teeth', 'Take a shower', 'Check e-mail', 'Walk dog'];
drop(event: CdkDragDrop<string[]>) {
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,
);
}
}
}
.example-container {
width: 400px;
max-width: 100%;
margin: 0 25px 25px 0;
display: inline-block;
vertical-align: top;
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
font-family: sans-serif;
}
h2 {
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
如果你有數量不定的已連接放置清單,可以使用 cdkDropListGroup 指令來自動設定連接。任何加到群組底下的新 cdkDropList 都會自動連接到所有其他清單。
<div cdkDropListGroup>
<!-- All lists in here will be connected. -->
@for (list of lists; track list) {
<div cdkDropList></div>
}
</div>
<div cdkDropListGroup>
<div class="example-container">
<h2>To do</h2>
<div
cdkDropList
[cdkDropListData]="todo"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (item of todo; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
</div>
<div class="example-container">
<h2>Done</h2>
<div
cdkDropList
[cdkDropListData]="done"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (item of done; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
</div>
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDropList,
CdkDropListGroup,
moveItemInArray,
transferArrayItem,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop connected sorting group
*/
@Component({
selector: 'cdk-drag-drop-connected-sorting-group-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropListGroup, CdkDropList, CdkDrag],
})
export class CdkDragDropConnectedSortingGroupExample {
todo = ['Get to work', 'Pick up groceries', 'Go home', 'Fall asleep'];
done = ['Get up', 'Brush teeth', 'Take a shower', 'Check e-mail', 'Walk dog'];
drop(event: CdkDragDrop<string[]>) {
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,
);
}
}
}
.example-container {
width: 400px;
max-width: 100%;
margin: 0 25px 25px 0;
display: inline-block;
vertical-align: top;
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
font-family: sans-serif;
}
h2 {
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
你可以使用 CDK_DROP_LIST_GROUP 注入權杖來參照 cdkDropListGroup 的執行個體。如需更多資訊,請參閱依賴注入指南與放置清單群組注入權杖 API。
依預設,使用者可以把 cdkDrag 元素從一個容器移到另一個已連接的容器。若要更精細地控制哪些元素可以放入容器,請使用 cdkDropListEnterPredicate。每當可拖曳元素進入新容器時,Angular 都會呼叫這個述詞。依述詞傳回 true 或 false,該項目可能會、也可能不會被允許進入新容器。
<div class="example-container">
<h2>Available numbers</h2>
<div
id="all"
cdkDropList
[cdkDropListData]="all"
cdkDropListConnectedTo="even"
class="example-list"
(cdkDropListDropped)="drop($event)"
[cdkDropListEnterPredicate]="noReturnPredicate"
>
@for (number of all; track number) {
<div class="example-box" [cdkDragData]="number" cdkDrag>{{ number }}</div>
}
</div>
</div>
<div class="example-container">
<h2>Even numbers</h2>
<div
id="even"
cdkDropList
[cdkDropListData]="even"
cdkDropListConnectedTo="all"
class="example-list"
(cdkDropListDropped)="drop($event)"
[cdkDropListEnterPredicate]="evenPredicate"
>
@for (number of even; track number) {
<div class="example-box" cdkDrag [cdkDragData]="number">{{ number }}</div>
}
</div>
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDropList,
moveItemInArray,
transferArrayItem,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop enter predicate
*/
@Component({
selector: 'cdk-drag-drop-enter-predicate-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropEnterPredicateExample {
all = [1, 2, 3, 4, 5, 6, 7, 8, 9];
even = [10];
drop(event: CdkDragDrop<number[]>) {
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,
);
}
}
/** Predicate function that only allows even numbers to be dropped into a list. */
evenPredicate(item: CdkDrag<number>) {
return item.data % 2 === 0;
}
/** Predicate function that doesn't allow items to be dropped into a list. */
noReturnPredicate() {
return false;
}
}
.example-container {
width: 400px;
max-width: 100%;
margin: 0 25px 25px 0;
display: inline-block;
vertical-align: top;
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
}
h2 {
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
分別設定 cdkDragData 或 cdkDropListData,就能把任意資料關聯到 cdkDrag 與 cdkDropList。你可以繫結到這兩個指令觸發的事件,這些事件會包含該資料,讓你能輕鬆辨識拖曳或放置互動的來源。
@for (list of lists; track list) {
<div cdkDropList [cdkDropListData]="list" (cdkDropListDropped)="drop($event)">
@for (item of list; track item) {
<div cdkDrag [cdkDragData]="item"></div>
}
</div>
}
依預設,使用者可以拖曳整個 cdkDrag 元素來移動它。若要限制使用者只能透過控點元素來拖曳,請把 cdkDragHandle 指令加到 cdkDrag 內部的元素上。你可以有任意數量的 cdkDragHandle 元素。
<div class="example-box" cdkDrag>
I can only be dragged using the handle
<div class="example-handle" cdkDragHandle>
<svg width="24px" fill="currentColor" viewBox="0 0 24 24">
<path
d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"
></path>
<path d="M0 0h24v24H0z" fill="none"></path>
</svg>
</div>
</div>
import {CdkDrag, CdkDragHandle} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop with a handle
*/
@Component({
selector: 'cdk-drag-drop-handle-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag, CdkDragHandle],
})
export class CdkDragDropHandleExample {}
.example-box {
width: 200px;
height: 200px;
padding: 10px;
box-sizing: border-box;
border: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
justify-content: center;
align-items: center;
text-align: center;
background: #fff;
border-radius: 4px;
position: relative;
z-index: 1;
font-family: sans-serif;
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);
}
.example-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);
}
.example-handle {
position: absolute;
top: 10px;
right: 10px;
color: #ccc;
cursor: move;
width: 24px;
height: 24px;
}
你可以使用 CDK_DRAG_HANDLE 注入權杖來參照 cdkDragHandle 的執行個體。如需更多資訊,請參閱依賴注入指南與拖曳控點注入權杖 API。
當 cdkDrag 元素被拖曳時,預覽元素會變得可見。依預設,預覽是原始元素的複製,位置就在使用者的游標旁邊。
若要自訂預覽,請透過 *cdkDragPreview 提供自訂範本。由於不會對元素的內容做任何假設,自訂預覽不會符合原始被拖曳元素的大小。若要讓拖曳預覽符合元素的大小,請把 true 傳給 matchSize 輸入。
複製出的元素會移除它的 id 屬性,以免頁面上有多個具有相同 id 的元素。這會導致任何以該 id 為目標的 CSS 都不會被套用。
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
@for (movie of movies; track movie) {
<div class="example-box" cdkDrag>
{{ movie.title }}
<img *cdkDragPreview [src]="movie.poster" [alt]="movie.title" />
</div>
}
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDragPreview,
CdkDropList,
moveItemInArray,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop custom preview
*/
@Component({
selector: 'cdk-drag-drop-custom-preview-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag, CdkDragPreview],
})
export class CdkDragDropCustomPreviewExample {
// tslint:disable:max-line-length
movies = [
{
title: 'Episode I - The Phantom Menace',
poster: 'https://upload.wikimedia.org/wikipedia/en/4/40/Star_Wars_Phantom_Menace_poster.jpg',
},
{
title: 'Episode II - Attack of the Clones',
poster:
'https://upload.wikimedia.org/wikipedia/en/3/32/Star_Wars_-_Episode_II_Attack_of_the_Clones_%28movie_poster%29.jpg',
},
{
title: 'Episode III - Revenge of the Sith',
poster:
'https://upload.wikimedia.org/wikipedia/en/9/93/Star_Wars_Episode_III_Revenge_of_the_Sith_poster.jpg',
},
{
title: 'Episode IV - A New Hope',
poster: 'https://upload.wikimedia.org/wikipedia/en/8/87/StarWarsMoviePoster1977.jpg',
},
{
title: 'Episode V - The Empire Strikes Back',
poster:
'https://upload.wikimedia.org/wikipedia/en/3/3f/The_Empire_Strikes_Back_%281980_film%29.jpg',
},
{
title: 'Episode VI - Return of the Jedi',
poster: 'https://upload.wikimedia.org/wikipedia/en/b/b2/ReturnOfTheJediPoster1983.jpg',
},
{
title: 'Episode VII - The Force Awakens',
poster:
'https://upload.wikimedia.org/wikipedia/en/a/a2/Star_Wars_The_Force_Awakens_Theatrical_Poster.jpg',
},
{
title: 'Episode VIII - The Last Jedi',
poster: 'https://upload.wikimedia.org/wikipedia/en/7/7f/Star_Wars_The_Last_Jedi.jpg',
},
{
title: 'Episode IX – The Rise of Skywalker',
poster:
'https://upload.wikimedia.org/wikipedia/en/a/af/Star_Wars_The_Rise_of_Skywalker_poster.jpg',
},
];
// tslint:enable:max-line-length
drop(event: CdkDragDrop<{title: string; poster: string}[]>) {
moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
}
}
.example-list {
width: 500px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: block;
background: white;
border-radius: 4px;
overflow: hidden;
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
你可以使用 CDK_DRAG_PREVIEW 注入權杖來參照 cdkDragPreview 的執行個體。如需更多資訊,請參閱依賴注入指南與拖曳預覽注入權杖 API。
依預設,Angular 會把 cdkDrag 預覽插入頁面的 <body>,以避免定位與溢位方面的問題。在某些情況下這可能不是理想的做法,因為預覽不會套用它繼承的樣式。
你可以使用 cdkDrag 上的 cdkDragPreviewContainer 輸入,改變 Angular 插入預覽的位置。可能的值如下:
或者,如果值是 global 或 parent,你可以修改 CDK_DRAG_CONFIG 注入權杖,更新設定中的 previewContainer。如需更多資訊,請參閱依賴注入指南、拖曳設定注入權杖 API與拖放設定 API。
當 cdkDrag 元素被拖曳時,該指令會建立一個佔位元素,顯示元素放下後將被放置的位置。依預設,佔位元素是被拖曳元素的複製。你可以使用 *cdkDragPlaceholder 指令,把佔位元素換成自訂的:
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
@for (movie of movies; track movie) {
<div class="example-box" cdkDrag>
<div class="example-custom-placeholder" *cdkDragPlaceholder></div>
{{ movie }}
</div>
}
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDragPlaceholder,
CdkDropList,
moveItemInArray,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop custom placeholder
*/
@Component({
selector: 'cdk-drag-drop-custom-placeholder-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag, CdkDragPlaceholder],
})
export class CdkDragDropCustomPlaceholderExample {
movies = [
'Episode I - The Phantom Menace',
'Episode II - Attack of the Clones',
'Episode III - Revenge of the Sith',
'Episode IV - A New Hope',
'Episode V - The Empire Strikes Back',
'Episode VI - Return of the Jedi',
'Episode VII - The Force Awakens',
'Episode VIII - The Last Jedi',
'Episode IX - The Rise of Skywalker',
];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
}
}
.example-list {
width: 500px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: block;
background: white;
border-radius: 4px;
overflow: hidden;
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-custom-placeholder {
background: #ccc;
border: dotted 3px #999;
min-height: 60px;
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
你可以使用 CDK_DRAG_PLACEHOLDER 注入權杖來參照 cdkDragPlaceholder 的執行個體。如需更多資訊,請參閱依賴注入指南與拖曳佔位元素注入權杖 API。
如果你想讓某個元素變得可拖曳,卻無法直接存取它,請設定 cdkDragRootElement 屬性。
該屬性接受一個選擇器,並向上查找 DOM,直到找到符合該選擇器的元素。如果找到元素,它就會變得可拖曳。這在讓對話框變得可拖曳等情況下很有用。
<button type="button" (click)="openDialog()">Open a draggable dialog</button>
<ng-template>
<div class="example-dialog-content" cdkDrag cdkDragRootElement=".cdk-overlay-pane">
Drag the dialog around!
</div>
</ng-template>
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Overlay, OverlayRef} from '@angular/cdk/overlay';
import {TemplatePortal} from '@angular/cdk/portal';
import {
AfterViewInit,
Component,
OnDestroy,
TemplateRef,
ViewContainerRef,
inject,
viewChild,
} from '@angular/core';
/**
* @title Drag&Drop with alternate root element
*/
@Component({
selector: 'cdk-drag-drop-root-element-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag],
})
export class CdkDragDropRootElementExample implements AfterViewInit, OnDestroy {
private _overlay = inject(Overlay);
private _viewContainerRef = inject(ViewContainerRef);
private _dialogTemplate = viewChild.required(TemplateRef);
private _overlayRef!: OverlayRef;
private _portal!: TemplatePortal;
ngAfterViewInit() {
this._portal = new TemplatePortal(this._dialogTemplate(), this._viewContainerRef);
this._overlayRef = this._overlay.create({
positionStrategy: this._overlay.position().global().centerHorizontally().centerVertically(),
hasBackdrop: true,
});
this._overlayRef.backdropClick().subscribe(() => this._overlayRef.detach());
}
ngOnDestroy() {
this._overlayRef.dispose();
}
openDialog() {
this._overlayRef.attach(this._portal);
}
}
.example-dialog-content {
width: 200px;
height: 200px;
border: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
cursor: move;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
border-radius: 4px;
font-family: sans-serif;
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);
}
.example-dialog-content: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);
}
或者,你可以修改 CDK_DRAG_CONFIG 注入權杖,更新設定中的 rootElementSelector。如需更多資訊,請參閱依賴注入指南、拖曳設定注入權杖 API與拖放設定 API。
依預設,不在 cdkDropList 中的 cdkDrag 元素,只有在使用者手動移動元素時,才會離開它一般的 DOM 位置。使用 cdkDragFreeDragPosition 輸入可以明確設定元素的位置。常見的使用案例是在使用者離開又返回後,恢復可拖曳元素的位置。
<p>
<button type="button" (click)="changePosition()">Change element position</button>
</p>
<div class="example-box" cdkDrag [cdkDragFreeDragPosition]="dragPosition">Drag me around</div>
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Programmatically setting the free drag position
*/
@Component({
selector: 'cdk-drag-drop-free-drag-position-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag],
})
export class CdkDragDropFreeDragPositionExample {
dragPosition = {x: 0, y: 0};
changePosition() {
this.dragPosition = {x: this.dragPosition.x + 50, y: this.dragPosition.y + 50};
}
}
.example-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
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;
font-family: sans-serif;
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);
}
.example-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);
}
若要防止使用者把 cdkDrag 元素拖到另一個元素之外,請把 CSS 選擇器傳給 cdkDragBoundary 屬性。這個屬性接受一個選擇器,並向上查找 DOM,直到找到符合的元素。如果找到符合項,該元素就成為邊界,可拖曳元素不能被拖到它之外。cdkDragBoundary 也可以在 cdkDrag 放在 cdkDropList 內部時使用。
<div class="example-boundary">
<div class="example-box" cdkDragBoundary=".example-boundary" cdkDrag>
I can only be dragged within the dotted container
</div>
</div>
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop boundary
*/
@Component({
selector: 'cdk-drag-drop-boundary-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag],
})
export class CdkDragDropBoundaryExample {}
.example-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
cursor: move;
display: inline-flex;
justify-content: center;
align-items: center;
text-align: center;
background: #fff;
border-radius: 4px;
margin-right: 25px;
position: relative;
z-index: 1;
box-sizing: border-box;
padding: 10px;
font-family: sans-serif;
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);
}
.example-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);
}
.example-boundary {
width: 400px;
height: 400px;
max-width: 100%;
border: dotted #ccc 2px;
}
或者,你可以修改 CDK_DRAG_CONFIG 注入權杖,更新設定中的 boundaryElement。如需更多資訊,請參閱依賴注入指南、拖曳設定注入權杖 API與拖放設定 API。
依預設,cdkDrag 允許在所有方向自由移動。若要把拖曳限制在特定軸上,請在 cdkDrag 上把 cdkDragLockAxis 設為 "x" 或 "y"。若要限制 cdkDropList 內多個可拖曳元素的拖曳,請改在 cdkDropList 上設定 cdkDropListLockAxis。
<div class="example-box" cdkDragLockAxis="y" cdkDrag>I can only be dragged up/down</div>
<div class="example-box" cdkDragLockAxis="x" cdkDrag>I can only be dragged left/right</div>
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop position locking
*/
@Component({
selector: 'cdk-drag-drop-axis-lock-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag],
})
export class CdkDragDropAxisLockExample {}
.example-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
cursor: move;
display: inline-flex;
justify-content: center;
align-items: center;
text-align: center;
background: #fff;
border-radius: 4px;
margin-right: 25px;
position: relative;
z-index: 1;
font-family: sans-serif;
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);
}
.example-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);
}
或者,你可以修改 CDK_DRAG_CONFIG 注入權杖,更新設定中的 lockAxis。如需更多資訊,請參閱依賴注入指南、拖曳設定注入權杖 API與拖放設定 API。
依預設,當使用者在 cdkDrag 上按住指標時,拖曳程序就會開始。在某些情況下這種行為可能不理想,例如觸控裝置上的全螢幕可拖曳元素,使用者在捲動頁面時可能不小心觸發拖曳事件。
你可以使用 cdkDragStartDelay 輸入來延遲拖曳程序。這個輸入會等使用者按住指標達到指定的毫秒數後,才開始拖曳元素。
<div class="example-box" cdkDrag [cdkDragStartDelay]="1000">Dragging starts after one second</div>
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Delay dragging
*/
@Component({
selector: 'cdk-drag-drop-delay-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag],
})
export class CdkDragDropDelayExample {}
.example-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
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;
font-family: sans-serif;
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);
}
.example-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);
}
或者,你可以修改 CDK_DRAG_CONFIG 注入權杖,更新設定中的 dragStartDelay。如需更多資訊,請參閱依賴注入指南、拖曳設定注入權杖 API與拖放設定 API。
如果你想停用特定拖曳項目的拖曳功能,請把 cdkDrag 項目上的 cdkDragDisabled 輸入設為 true 或 false。你可以使用 cdkDropList 上的 cdkDropListDisabled 輸入停用整個清單。也可以透過 cdkDragHandle 上的 cdkDragHandleDisabled 停用特定的控點。
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
@for (item of items; track item) {
<div class="example-box" cdkDrag [cdkDragDisabled]="item.disabled">{{ item.value }}</div>
}
</div>
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop disabled
*/
@Component({
selector: 'cdk-drag-drop-disabled-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropDisabledExample {
items = [
{value: 'I can be dragged', disabled: false},
{value: 'I cannot be dragged', disabled: true},
{value: 'I can also be dragged', disabled: false},
];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.items, event.previousIndex, event.currentIndex);
}
}
.example-list {
width: 500px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: block;
background: white;
border-radius: 4px;
overflow: hidden;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.example-box.cdk-drag-disabled {
background: #ccc;
cursor: not-allowed;
user-select: none;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
或者,你可以修改 CDK_DRAG_CONFIG 注入權杖,更新設定中的 draggingDisabled。如需更多資訊,請參閱依賴注入指南、拖曳設定注入權杖 API與拖放設定 API。
依預設,cdkDropList 指令假設清單是垂直的。把 cdkDropListOrientation 屬性設為 horizontal,就能改變這一點。
<div
cdkDropList
cdkDropListOrientation="horizontal"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (timePeriod of timePeriods; track timePeriod) {
<div class="example-box" cdkDrag>{{ timePeriod }}</div>
}
</div>
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop horizontal sorting
*/
@Component({
selector: 'cdk-drag-drop-horizontal-sorting-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropHorizontalSortingExample {
timePeriods = [
'Bronze age',
'Iron age',
'Middle ages',
'Early modern period',
'Long nineteenth century',
];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.timePeriods, event.previousIndex, event.currentIndex);
}
}
.example-list {
width: 1000px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: flex;
flex-direction: row;
background: white;
border-radius: 4px;
overflow: hidden;
}
.example-box {
padding: 20px 10px;
border-right: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
flex-grow: 1;
flex-basis: 0;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
或者,你可以修改 CDK_DRAG_CONFIG 注入權杖,更新設定中的 listOrientation。如需更多資訊,請參閱依賴注入指南、拖曳設定注入權杖 API與拖放設定 API。
依預設,cdkDropList 使用 CSS transform 移動可拖曳元素來進行排序。這讓排序可以動畫呈現,提供更好的使用者體驗。不過,這也有個缺點:放置清單只能在單一方向運作,也就是垂直或水平。
如果你有需要換到新行的可排序清單,可以把 cdkDropListOrientation 屬性設為 mixed。這會讓清單改用不同的元素排序策略,也就是在 DOM 中移動元素。不過,清單就無法再以動畫呈現排序動作了。
<div
cdkDropList
cdkDropListOrientation="mixed"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (item of items; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop horizontal wrapping list
*/
@Component({
selector: 'cdk-drag-drop-mixed-sorting-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropMixedSortingExample {
items = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine'];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.items, event.previousIndex, event.currentIndex);
}
}
.example-list {
display: flex;
flex-wrap: wrap;
width: 505px;
max-width: 100%;
gap: 15px;
padding: 15px;
border: solid 1px #ccc;
min-height: 60px;
border-radius: 4px;
overflow: hidden;
}
.example-box {
padding: 20px 10px;
border: solid 1px #ccc;
border-radius: 4px;
color: rgba(0, 0, 0, 0.87);
display: inline-block;
box-sizing: border-box;
cursor: move;
background: white;
text-align: center;
font-size: 14px;
min-width: 115px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
依預設,cdkDrag 元素可以被排序到 cdkDropList 內的任何位置。若要改變這個行為,請設定接受函式的 cdkDropListSortPredicate 屬性。每當可拖曳元素即將被移到放置清單內的新索引時,就會呼叫這個述詞函式。如果述詞傳回 true,該項目會被移到新索引;否則會保持目前的位置。
<div
cdkDropList
class="example-list"
(cdkDropListDropped)="drop($event)"
[cdkDropListSortPredicate]="sortPredicate"
>
@for (number of numbers; track number) {
<div class="example-box" [cdkDragData]="number" cdkDrag>{{ number }}</div>
}
</div>
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop sort predicate
*/
@Component({
selector: 'cdk-drag-drop-sort-predicate-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropSortPredicateExample {
numbers = [1, 2, 3, 4, 5, 6, 7, 8];
drop(event: CdkDragDrop<unknown>) {
moveItemInArray(this.numbers, event.previousIndex, event.currentIndex);
}
/**
* Predicate function that only allows even numbers to be
* sorted into even indices and odd numbers at odd indices.
*/
sortPredicate(index: number, item: CdkDrag<number>) {
return (index + 1) % 2 === item.data % 2;
}
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
width: 400px;
max-width: 100%;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
有些情況下,可拖曳元素可以從一個 cdkDropList 拖到另一個,但使用者不應該能在來源清單內排序它們。對於這些情況,請加入 cdkDropListSortingDisabled 屬性,防止 cdkDropList 中的可拖曳元素進行排序。如果被拖曳的元素沒有被拖到新的有效位置,這能保留它在來源清單中的初始位置。
<div cdkDropListGroup>
<div class="example-container">
<h2>Available items</h2>
<div
cdkDropList
[cdkDropListData]="items"
class="example-list"
cdkDropListSortingDisabled
(cdkDropListDropped)="drop($event)"
>
@for (item of items; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
</div>
<div class="example-container">
<h2>Shopping basket</h2>
<div
cdkDropList
[cdkDropListData]="basket"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (item of basket; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
</div>
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDropList,
CdkDropListGroup,
moveItemInArray,
transferArrayItem,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop disabled sorting
*/
@Component({
selector: 'cdk-drag-drop-disabled-sorting-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropListGroup, CdkDropList, CdkDrag],
})
export class CdkDragDropDisabledSortingExample {
items = ['Carrots', 'Tomatoes', 'Onions', 'Apples', 'Avocados'];
basket = ['Oranges', 'Bananas', 'Cucumbers'];
drop(event: CdkDragDrop<string[]>) {
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,
);
}
}
}
.example-container {
width: 400px;
max-width: 100%;
margin: 0 25px 25px 0;
display: inline-block;
vertical-align: top;
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
}
h2 {
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
或者,你可以修改 CDK_DRAG_CONFIG 注入權杖,更新設定中的 sortingDisabled。如需更多資訊,請參閱依賴注入指南、拖曳設定注入權杖 API與拖放設定 API。
依預設,當項目從一個清單拖到另一個清單時,它會從原始清單移出。不過,你可以設定這些指令來複製項目,讓原始項目留在來源清單中。
若要啟用複製,你可以設定 cdkDropListHasAnchor 輸入。這會告訴 cdkDropList 建立一個「anchor」元素,它會留在原始容器中,不隨項目移動。如果使用者把項目移回原始容器,這個 anchor 會自動被移除。你可以把 .cdk-drag-anchor CSS 類別作為目標,來為 anchor 元素設定樣式。
把 cdkDropListHasAnchor 與 cdkDropListSortingDisabled 組合起來,就能建立一份讓使用者可以複製項目、但無法重新排序來源清單的清單(例如產品清單與購物車)。
<div class="example-container">
<h2>Products</h2>
<div
cdkDropList
[cdkDropListData]="products"
[cdkDropListConnectedTo]="[cartList]"
cdkDropListSortingDisabled
cdkDropListHasAnchor
class="example-list"
>
@for (product of products; track $index) {
<div class="example-box" cdkDrag [cdkDragData]="product">{{ product }}</div>
}
</div>
</div>
<div class="example-container">
<h2>Shopping cart</h2>
<div
cdkDropList
#cartList="cdkDropList"
[cdkDropListData]="cart"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (product of cart; track $index) {
<div class="example-box" cdkDrag>{{ product }}</div>
}
</div>
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDropList,
copyArrayItem,
moveItemInArray,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop copy between lists
*/
@Component({
selector: 'cdk-drag-drop-copy-list-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropCopyListExample {
products = ['Bananas', 'Oranges', 'Bread', 'Butter', 'Soda', 'Eggs'];
cart = ['Tomatoes'];
drop(event: CdkDragDrop<string[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
copyArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex,
);
}
}
}
.example-container {
width: 400px;
max-width: 100%;
margin: 0 25px 25px 0;
display: inline-block;
vertical-align: top;
font-family: sans-serif;
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
}
h2 {
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
拖放功能支援以下兩種動畫:
- 在清單內排序可拖曳元素
- 把可拖曳元素從使用者放下的位置移到清單內的最終位置
若要設定你的動畫,請定義一個以 transform 屬性為目標的 CSS transition。下列類別可以用於動畫:
cdkDrag 與 cdkDropList 這兩個指令都只套用功能運作所必需的樣式。應用程式可以把這些指定的 CSS 類別作為目標,來自訂樣式。
如果你的可拖曳項目位於可捲動容器內(例如帶有 overflow: auto 的 div),除非該可捲動容器有 cdkScrollable 指令,否則自動捲動將無法運作。沒有它,CDK 在拖曳操作期間就無法偵測或控制容器的捲動行為。
CDK 的拖放功能可以與不同的元件整合。常見的使用案例包括可排序的 MatTable 元件與可排序的 MatTabGroup 元件。