На этой странице описаны директивы drag and drop, которые позволяют быстро создавать интерфейсы перетаскивания со следующими возможностями:
- Свободное перетаскивание
- Создание списка переупорядочиваемых перетаскиваемых элементов
- Перенос перетаскиваемых элементов между списками
- Анимации перетаскивания
- Блокировка перетаскиваемых элементов по оси или внутри элемента
- Добавление пользовательских ручек перетаскивания
- Добавление превью при перетаскивании
- Добавление пользовательского placeholder при перетаскивании
Полный справочник API см. на странице справочника API drag and drop Angular CDK.
Component Dev Kit (CDK) — набор примитивов поведения для создания компонентов. Чтобы использовать директивы drag and drop, сначала установите @angular/cdk из npm. Это можно сделать из терминала с помощью Angular CLI:
Чтобы использовать drag and drop, импортируйте необходимое из директив в компонент.
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 к родительскому элементу, чтобы сгруппировать перетаскиваемые элементы в переупорядочиваемую коллекцию. Это определяет, куда можно сбрасывать перетаскиваемые элементы. Перетаскиваемые элементы в группе drop list автоматически перестраиваются при перемещении элемента.
Директивы drag and drop не обновляют модель данных. Чтобы обновить модель данных, подпишитесь на событие 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);
}
Можно использовать injection token CDK_DROP_LIST, который позволяет ссылаться на экземпляры cdkDropList. Подробнее см. руководство по внедрению зависимостей и API injection token drop list.
Директива cdkDropList поддерживает перенос перетаскиваемых элементов между связанными drop list. Есть два способа связать один или несколько экземпляров cdkDropList:
- Установить свойство
cdkDropListConnectedTo на другой drop list.
- Обернуть элементы в элемент с атрибутом
cdkDropListGroup.
Директива cdkDropListConnectedTo работает как с прямой ссылкой на другой cdkDropList, так и со ссылкой на id другого drop-контейнера.
<!-- 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, если у вас неизвестное количество связанных drop list — она автоматически настраивает соединение. Любой новый 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);
}
Можно использовать injection token CDK_DROP_LIST_GROUP, который позволяет ссылаться на экземпляры cdkDropListGroup. Подробнее см. руководство по внедрению зависимостей и API injection token drop list group.
По умолчанию пользователь может перемещать элементы 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);
}
Можно связать произвольные данные как с cdkDrag, так и с cdkDropList, установив соответственно cdkDragData или cdkDropListData. Можно подписаться на события, генерируемые обеими директивами, которые будут включать эти данные, что позволяет легко определить источник взаимодействия drag или drop.
@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;
}
Можно использовать injection token CDK_DRAG_HANDLE, который позволяет ссылаться на экземпляры cdkDragHandle. Подробнее см. руководство по внедрению зависимостей и API injection token drag handle.
Элемент превью становится видимым, когда элемент cdkDrag перетаскивается. По умолчанию превью — это клон оригинального элемента, расположенный рядом с курсором пользователя.
Чтобы настроить превью, предоставьте пользовательский шаблон через *cdkDragPreview. Пользовательское превью не будет соответствовать размеру оригинального перетаскиваемого элемента, поскольку не делаются предположения о содержимом элемента. Чтобы превью соответствовало размеру элемента, передайте true во вход matchSize.
У клонированного элемента удаляется атрибут id, чтобы избежать нескольких элементов с одинаковым id на странице. Из-за этого CSS, нацеленный на этот id, не будет применён.
<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);
}
Можно использовать injection token CDK_DRAG_PREVIEW, который позволяет ссылаться на экземпляры cdkDragPreview. Подробнее см. руководство по внедрению зависимостей и API injection token drag preview.
По умолчанию Angular вставляет превью cdkDrag в <body> страницы, чтобы избежать проблем с позиционированием и overflow. В некоторых случаях это может быть нежелательно, поскольку превью не получит унаследованные стили.
Можно изменить место вставки превью с помощью входа cdkDragPreviewContainer на cdkDrag. Возможные значения:
Альтернативно можно изменить injection token CDK_DRAG_CONFIG, чтобы обновить previewContainer в конфигурации, если значение — global или parent. Подробнее см. руководство по внедрению зависимостей, API injection token drag config и API drag drop config.
Пока элемент cdkDrag перетаскивается, директива создаёт элемент placeholder, показывающий, куда элемент будет помещён при сбросе. По умолчанию placeholder — это клон перетаскиваемого элемента. Можно заменить placeholder пользовательским с помощью директивы *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);
}
Можно использовать injection token CDK_DRAG_PLACEHOLDER, который позволяет ссылаться на экземпляры cdkDragPlaceholder. Подробнее см. руководство по внедрению зависимостей и API injection token drag placeholder.
Установите атрибут 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);
}
Альтернативно можно изменить injection token CDK_DRAG_CONFIG, чтобы обновить rootElementSelector в конфигурации. Подробнее см. руководство по внедрению зависимостей, API injection token drag config и API drag drop config.
По умолчанию элементы cdkDrag, не находящиеся в cdkDropList, перемещаются из обычной 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;
}
Альтернативно можно изменить injection token CDK_DRAG_CONFIG, чтобы обновить boundaryElement в конфигурации. Подробнее см. руководство по внедрению зависимостей, API injection token drag config и API drag drop config.
По умолчанию cdkDrag позволяет свободное перемещение во всех направлениях. Чтобы ограничить перетаскивание по определённой оси, установите cdkDragLockAxis в «x» или «y» на cdkDrag. Чтобы ограничить перетаскивание для нескольких перетаскиваемых элементов внутри cdkDropList, установите cdkDropListLockAxis на cdkDropList.
<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);
}
Альтернативно можно изменить injection token CDK_DRAG_CONFIG, чтобы обновить lockAxis в конфигурации. Подробнее см. руководство по внедрению зависимостей, API injection token drag config и API drag drop config.
По умолчанию, когда пользователь нажимает указателем на 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);
}
Альтернативно можно изменить injection token CDK_DRAG_CONFIG, чтобы обновить dragStartDelay в конфигурации. Подробнее см. руководство по внедрению зависимостей, API injection token drag config и API drag drop config.
Чтобы отключить перетаскивание для конкретного элемента, установите вход cdkDragDisabled на элементе cdkDrag в true или false. Можно отключить весь список с помощью входа cdkDropListDisabled на cdkDropList. Также можно отключить конкретную ручку через cdkDragHandleDisabled на cdkDragHandle.
<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);
}
Альтернативно можно изменить injection token CDK_DRAG_CONFIG, чтобы обновить draggingDisabled в конфигурации. Подробнее см. руководство по внедрению зависимостей, API injection token drag config и API drag drop config.
По умолчанию директива 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);
}
Альтернативно можно изменить injection token CDK_DRAG_CONFIG, чтобы обновить listOrientation в конфигурации. Подробнее см. руководство по внедрению зависимостей, API injection token drag config и API drag drop config.
По умолчанию cdkDropList сортирует перетаскиваемые элементы, перемещая их с помощью CSS transform. Это позволяет анимировать сортировку, что улучшает пользовательский опыт. Однако drop list работает только в одном направлении: вертикально или горизонтально.
Если у вас сортируемый список, который должен переноситься на новые строки, установите атрибут 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, который принимает функцию. Функция-предикат вызывается, когда перетаскиваемый элемент собирается переместиться на новый индекс в drop list. Если предикат возвращает 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);
}
Альтернативно можно изменить injection token CDK_DRAG_CONFIG, чтобы обновить sortingDisabled в конфигурации. Подробнее см. руководство по внедрению зависимостей, API injection token drag config и API drag drop config.
По умолчанию, когда элемент перетаскивается из одного списка в другой, он удаляется из исходного списка. Однако можно настроить директивы на копирование элемента, оставляя оригинал в исходном списке.
Чтобы включить копирование, установите вход cdkDropListHasAnchor. Это указывает cdkDropList создать «якорный» элемент, который остаётся в исходном контейнере и не перемещается вместе с элементом. Если пользователь вернёт элемент в исходный контейнер, якорь удаляется автоматически. Якорный элемент можно стилизовать, нацелившись на CSS-класс .cdk-drag-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);
}
Перетаскивание поддерживает анимации для:
- Сортировки перетаскиваемого элемента внутри списка
- Перемещения перетаскиваемого элемента из позиции, куда пользователь его сбросил, в финальную позицию внутри списка
Чтобы настроить анимации, определите CSS-переход, нацеленный на свойство transform. Для анимаций можно использовать следующие классы:
Директивы cdkDrag и cdkDropList применяют только необходимые стили для функциональности. Приложения могут настраивать стили, нацеливаясь на указанные CSS-классы.
Если перетаскиваемые элементы находятся внутри прокручиваемого контейнера (например, div с overflow: auto), автоматическая прокрутка не будет работать, пока прокручиваемый контейнер не имеет директивы cdkScrollable. Без неё CDK не может обнаружить или управлять поведением прокрутки контейнера во время операций перетаскивания.
Функциональность drag-and-drop CDK может интегрироваться с различными компонентами. Типичные случаи — сортируемые компоненты MatTable и сортируемые компоненты MatTabGroup.