Атрибут draggable включает или выключает перетаскивание элемента мышью. Это глобальный атрибут - его можно прописать любому тегу, но одного атрибута мало: чтобы перетаскивание к чему-то привело, нужны ещё и обработчики событий drag-and-drop.
<div draggable="true">Этот блок можно перетащить</div>
В этой статье
- Атрибут draggable: что делает и что тащится по умолчанию
- Полный цикл drag-and-drop: dragstart, dragover, drop
- Как отключить перетаскивание картинки, включённое по умолчанию
- Ограничения: недоступно с клавиатуры и плохо работает с сенсорным экраном
- Частые ошибки с атрибутом draggable
- Шпаргалка по атрибуту draggable
Атрибут draggable: что делает и что тащится по умолчанию
У draggable всего два допустимых значения, и оба нужно писать явно - это перечислимый атрибут, а не булевый.
| Значение | Что происходит |
|---|---|
| true | Элемент можно перетащить мышью |
| false | Элемент перетащить нельзя - и это значение по умолчанию для большинства тегов |
| Картинки и ссылки без атрибута | Перетаскиваются сами, браузер включает это поведение по умолчанию |
| Остальные теги без атрибута | Не перетаскиваются - как будто записано draggable="false" |
У draggable нет короткой записи без значения - в отличие от contenteditable, где contenteditable без значения равнозначен contenteditable="true". Запись draggable без значения браузер читает как draggable="false" - перетаскивание не включится.
Пример: draggable="true", "false" и без значения рядом
По-настоящему перетаскивается только первый блок - у короткой записи без значения перетаскивания нет вовсе.
Полный цикл drag-and-drop: dragstart, dragover, drop
Сам по себе draggable только позволяет начать перетаскивание - что произойдёт дальше, решают события. dragstart срабатывает на перетаскиваемом элементе в начале и обычно записывает данные в event.dataTransfer. dragover срабатывает на элементе под курсором на всё время перетаскивания, и в нём обязательно нужно вызвать preventDefault() - иначе браузер не разрешит сброс в этом месте. drop срабатывает в момент отпускания кнопки мыши и читает те же данные обратно через event.dataTransfer.
Пример: перетащить карточку в зону сброса
<div draggable="true" id="item">Карточка #123</div>
<div id="zone">Отпустите здесь</div>
item.addEventListener("dragstart", e => {
e.dataTransfer.setData("text/plain", "card-123");
});
zone.addEventListener("dragover", e => e.preventDefault());
zone.addEventListener("drop", e => {
e.preventDefault();
var data = e.dataTransfer.getData("text/plain");
});
dragstart записывает данные, dragover с preventDefault() разрешает сброс, drop считывает те же данные обратно.
Как отключить перетаскивание картинки, включённое по умолчанию
Картинки перетаскиваются мышью без единого атрибута - так браузер ведёт себя по умолчанию, и это часто мешает, когда картинка сама находится внутри области, которую пользователь перетаскивает по-другому (слайдер, карточка, canvas-редактор). Чтобы выключить это поведение, картинке прописывают draggable="false" явно - атрибута без значения или false по умолчанию здесь недостаточно, потому что у картинки свой браузерный draggable включён независимо от разметки.
Пример: обычная картинка и картинка с draggable="false"
<img src="cat.jpg" alt="Кот">
<img src="cat.jpg" alt="Кот" draggable="false">
Первая картинка тащится сама, у второй с draggable="false" перетаскивание заблокировано.
Ограничения: недоступно с клавиатуры и плохо работает с сенсорным экраном
У встроенного в браузер drag-and-drop два существенных ограничения. С клавиатуры такое перетаскивание недоступно вовсе - нет ни одной комбинации клавиш, которая заменяла бы зажатую кнопку мыши и движение курсора, поэтому человек, который не пользуется мышью, в принципе не может выполнить это действие. На сенсорных экранах нативный drag-and-drop тоже работает ненадёжно - жест перетаскивания легко путается с прокруткой страницы. Если перетаскивание - единственный способ выполнить действие на странице, часть посетителей просто не сможет им воспользоваться, поэтому рядом обычно оставляют кнопки или другой способ сделать то же самое.
Частые ошибки с атрибутом draggable
Сам атрибут draggable путают редко - ошибки чаще случаются в обработчиках событий, которые должны идти рядом.
| Ошибка | Что происходит | Как исправить |
|---|---|---|
| Пишут draggable без значения, как contenteditable | Браузер читает короткую запись как draggable="false" - перетаскивание не включается | Писать значение явно: draggable="true" |
| Прописывают draggable="true" и ждут, что перетаскивание куда-то приведёт | Без обработчиков dragstart/dragover/drop элемент просто сдвигается и возвращается на место | Добавлять обработчики событий - draggable только разрешает начать перетаскивание |
| Забывают preventDefault() в dragover | Браузер не считает место подходящим для сброса, и событие drop не срабатывает вовсе | Вызывать e.preventDefault() в обработчике dragover |
| Пытаются отключить перетаскивание картинки через CSS | pointer-events и подобные свойства не влияют на нативный drag - картинка продолжает тащиться | Использовать именно draggable="false" на самой картинке |
| Делают drag-and-drop единственным способом переставить элементы списка | Часть пользователей не может перетаскивать мышью - ни с клавиатуры, ни на части сенсорных экранов | Добавлять кнопки «вверх»/«вниз» или другой способ изменить порядок |
Шпаргалка по атрибуту draggable
| Правило | Что запомнить |
|---|---|
| Где работает | На любом теге (глобальный атрибут), кроме SVG |
| true | Элемент можно перетащить мышью |
| false | Элемент перетащить нельзя (значение по умолчанию) |
| Короткая запись без значения | Равнозначна false - в отличие от contenteditable, работает только явное значение |
| Картинки и ссылки | Перетаскиваются по умолчанию без атрибута |
| dragstart | Начало перетаскивания, запись данных в dataTransfer |
| dragover | Нужен preventDefault(), иначе drop не сработает |
| drop | Момент отпускания, чтение данных из dataTransfer |
| Клавиатура | Нативный drag-and-drop недоступен без мыши - нужна альтернатива |