[JavaScript, Программирование, Разработка веб-сайтов] Drag'n'Drop API: пример использования
Автор
Сообщение
news_bot ®
Стаж: 7 лет 2 месяца
Сообщений: 27286

Доброго времени суток, друзья!
В данном туториале мы рассмотрим встроенный механизм перетаскивания элементов на странице.
Справедливости ради следует отметить, что указанный механизм можно реализовать с помощью событий мыши, как показывает Илья Кантор в своем учебнике, однако мы будем использовать нативные средства, опираясь на спецификацию.
Поддержка технологии:

Превью:

Наша задача состоит в следующем: реализовать список задач, состоящий из трех колонок: все задачи, задачи, находящиеся в процессе выполнения, завершенные задачи. Разумеется, приложение должно предусматривать возможность добавления и удаления задач. Кроме того, должна быть предусмотрена возможность произвольного расположения задач. Это одна из наиболее интересных частей туториала — отслеживание элемента, находящегося под перетаскиваемым, и определение того, где должен располагаться перетаскиваемый элемент, над или под отслеживаемым.
Для стилизации будет использоваться Bootstrap.
Если вам это интересно, прошу следовать за мной.
Разметка:
<head>
<!-- Bootstrap CSS -->
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z"
crossorigin="anonymous"
/>
<!-- custom CSS -->
<link rel="stylesheet" href="style.css" />
</head>
<body class="container">
<h1>Drag & Drop Example</h1>
<main class="row">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Enter new todo: </span>
</div>
<input
type="text"
class="form-control"
placeholder="todo4"
data-name="todo-input"
/>
<div class="input-group-append">
<button class="btn btn-success" data-name="add-btn">Add</button>
</div>
</div>
<div class="col-4">
<h3>Todos</h3>
<ul class="list-group" data-name="todos-list">
<li class="list-group-item" data-id="1" draggable="true">
<p>todo1</p>
<button
class="btn btn-outline-danger btn-sm"
data-name="remove-btn"
>
X
</button>
</li>
<li class="list-group-item" data-id="2" draggable="true">
<p>todo2</p>
<button
class="btn btn-outline-danger btn-sm"
data-name="remove-btn"
>
X
</button>
</li>
<li class="list-group-item" data-id="3" draggable="true">
<p>todo3</p>
<button
class="btn btn-outline-danger btn-sm"
data-name="remove-btn"
>
X
</button>
</li>
</ul>
</div>
<div class="col-4">
<h3>In Progress</h3>
<ul class="list-group" data-name="in-progress-list"></ul>
</div>
<div class="col-4">
<h3>Completed</h3>
<ul class="list-group" data-name="completed-list"></ul>
</div>
</main>
<!-- custom JS -->
<script src="script.js"></script>
</body>
Здесь у нас имеется контейнер с полем для ввода текста задачи и кнопкой для ее добавления в список (input-group), а также три контейнера-колонки (list-group) для всех задач (todos-list), задач в процессе выполнения (in-progress-list) и завершенных задач (completed-list). Что касается атрибутов «data», то они предназначены для разделения стилизации и управления: классы — для стилизации, data — для управления.
Стили:
body {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #222;
}
main {
max-width: 600px;
}
.input-group {
margin: 1rem;
}
.list-group {
min-height: 100px;
height: 100%;
}
.list-group-item {
display: flex;
justify-content: space-between;
align-items: center;
}
div + div {
border-right: 1px dotted #222;
}
h3 {
text-align: center;
}
p {
margin: 0;
}
.completed p {
text-decoration: line-through;
}
.in-progress p {
border-bottom: 1px dashed #222;
}
.drop {
background: #eee;
border-radius: 4px;
}
Классы «in-progress» и «completed» служат индикаторами нахождения задачи в соответствующей колонке. Класс «drop» предназначен для визуализации попадания задачи в зону для «бросания».
Прежде чем переходить к скрипту, отметим, что нами будут использоваться далеко не все события перетаскивания, но большинство из основных.
Определяем главный контейнер, в котором будет осуществляться поиск элементов и которому будет делегирована обработка событий:
const main = document.querySelector("main");
Реализуем добавление и удаление задач через обработку клика:
main.addEventListener("click", (e) => {
// нас интересует только нажатие кнопки
if (e.target.tagName === "BUTTON") {
// получаем название кнопки из атрибута "data-name"
const { name } = e.target.dataset;
// если перед нами кнопка для добавления задачи в список
if (name === "add-btn") {
// определяем поле для ввода текста задачи
const todoInput = main.querySelector('[data-name="todo-input"]');
// если оно не является пустым
if (todoInput.value.trim() !== "") {
// получаем текст задачи
const value = todoInput.value;
// создаем шаблон задачи
const template = `
<li class="list-group-item" draggable="true" data-id="${Date.now()}">
<p>${value}</p>
<button class="btn btn-outline-danger btn-sm" data-name="remove-btn">X</button>
</li>
`;
// находим список задач
const todosList = main.querySelector('[data-name="todos-list"]');
// добавляем в него шаблон задачи
todosList.insertAdjacentHTML("beforeend", template);
// очищаем поле для ввода текста задачи
todoInput.value = "";
}
// если перед нами кнопка для удаления задачи
} else if (name === "remove-btn") {
// просто удаляем ее
e.target.parentElement.remove();
}
}
});
Переходим непосредственно к перетаскиванию.
Для начала реализуем попадание в зону для «бросание» и уход из нее посредством добавления/удаления соответствующего класса:
main.addEventListener("dragenter", (e) => {
// нас интересуют только колонки
if (e.target.classList.contains("list-group")) {
e.target.classList.add("drop");
}
});
main.addEventListener("dragleave", (e) => {
if (e.target.classList.contains("drop")) {
e.target.classList.remove("drop");
}
});
Далее обрабатываем начало перетаскивания:
main.addEventListener("dragstart", (e) => {
// нас интересует только задача
if (e.target.classList.contains("list-group-item")) {
// сохраняем идентификатор задачи в объекте "dataTransfer" в виде обычного текста;
// dataTransfer также позволяет сохранять HTML - text/html,
// но в данном случае нам это ни к чему
e.dataTransfer.setData("text/plain", e.target.dataset.id);
}
});
Теперь нам нужно каким-то образом отслеживать элемент, находящийся под перетаскиваемым. Это необходимо для того, чтобы произвольно располагать задачи в списке, т.е. менять задачи в колонке местами. При обработке события «mousemove» для этого используется метод «elementFromPoint(x, y)». Прелесть рассматриваемого интерфейса состоит в том, что для определения «низлежащего» элемента нам достаточно обработать событие «dragover»:
// создаем переменную для хранения "низлежащего" элемента
let elemBelow = "";
main.addEventListener("dragover", (e) => {
// отключаем стандартное поведение браузера;
// это необходимо сделать в любом случае
e.preventDefault();
// записываем в переменную целевой элемент;
// валидацию сделаем позже
elemBelow = e.target;
});
Наконец, обрабатываем событие «drop» («бросание»):
main.addEventListener("drop", (e) => {
// находим перетаскиваемую задачу по идентификатору, записанному в dataTransfer
const todo = main.querySelector(
`[data-id="${e.dataTransfer.getData("text/plain")}"]`
);
// прекращаем выполнение кода, если задача и элемент - одно и тоже
if (elemBelow === todo) {
return;
}
// если элементом является параграф или кнопка, значит, нам нужен их родительский элемент
if (elemBelow.tagName === "P" || elemBelow.tagName === "BUTTON") {
elemBelow = elemBelow.parentElement;
}
// на всякий случай еще раз проверяем, что имеем дело с задачей
if (elemBelow.classList.contains("list-group-item")) {
// нам нужно понять, куда помещать перетаскиваемый элемент:
// до или после низлежащего;
// для этого необходимо определить центр низлежащего элемента
// и положение курсора относительно этого центра (выше или ниже)
// определяем центр
const center =
elemBelow.getBoundingClientRect().y +
elemBelow.getBoundingClientRect().height / 2;
// если курсор находится ниже центра
// значит, перетаскиваемый элемент должен быть помещен под низлежащим
// иначе, перед ним
if (e.clientY > center) {
if (elemBelow.nextElementSibling !== null) {
elemBelow = elemBelow.nextElementSibling;
} else {
return;
}
}
elemBelow.parentElement.insertBefore(todo, elemBelow);
// рокировка элементов может происходить в разных колонках
// необходимо убедиться, что задачи будут визуально идентичными
todo.className = elemBelow.className;
}
// если целью является колонка
if (e.target.classList.contains("list-group")) {
// просто добавляем в нее перетаскиваемый элемент
// это приведет к автоматическому удалению элемента из "родной" колонки
e.target.append(todo);
// удаляем индикатор зоны для "бросания"
if (e.target.classList.contains("drop")) {
e.target.classList.remove("drop");
}
// визуальное оформление задачи в зависимости от колонки, в которой она находится
const { name } = e.target.dataset;
if (name === "completed-list") {
if (todo.classList.contains("in-progress")) {
todo.classList.remove("in-progress");
}
todo.classList.add("completed");
} else if (name === "in-progress-list") {
if (todo.classList.contains("completed")) {
todo.classList.remove("completed");
}
todo.classList.add("in-progress");
} else {
todo.className = "list-group-item";
}
}
});
Вот и все. Как видите, ничего сложного. Зато какие возможности по добавлению интерактивности на страницу. Осталось дождаться, когда мобильные браузеры реализуют данную технологию, и будет всем счастье.
Надеюсь, вы нашли для себя что-нибудь интересное. Благодарю за внимание и хорошего дня.
===========
Источник:
habr.com
===========
Похожие новости:
- [JavaScript, Angular, ReactJS, TypeScript] Простые TypeScript-хитрости, которые позволят масштабировать ваши приложения бесконечно
- [Анализ и проектирование систем, Программирование, Промышленное программирование, Системы управления версиями] О системах контроля версий
- [Программирование, Алгоритмы, Научно-популярное] Можно ли научить обезьяну программировать? Ждёт ли нас поколение специалистов-бабуинов
- [Разработка веб-сайтов, JavaScript, Программирование, VueJS] Vue 3.0 — первый взгляд
- [Информационная безопасность, Программирование, C++, ООП] C++: Коварство и Любовь, или Да что вообще может пойти не так? (перевод)
- [Программирование] Деконструкция LSP
- [Карьера в IT-индустрии, Удалённая работа, Программирование] «Работать на пляже не для меня — работа и отдых идут отдельно, даже на удалёнке». Алексей Катаев об удалённой работе
- [Беспроводные технологии, Программирование микроконтроллеров] Как подключить АЦП HX711 к NRF52832
- [C, JavaScript, Интернет вещей, Программирование микроконтроллеров, Разработка для интернета вещей] Термостат на ThingJS (beta)
- [.NET, C#, Программирование, Проектирование и рефакторинг, Тестирование IT-систем] Мне было стыдно за свой интерпрайз-код настолько, что я сделал свой велосипед. За него стыдно меньше
Теги для поиска: #_javascript, #_programmirovanie (Программирование), #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_draganddrop, #_drag_and_drop, #_drag&drop, #_peretaskivanie (перетаскивание), #_brosanie (бросание), #_javascript, #_programmirovanie (
Программирование
), #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 27-Апр 05:21
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 7 лет 2 месяца |
|
![]() Доброго времени суток, друзья! В данном туториале мы рассмотрим встроенный механизм перетаскивания элементов на странице. Справедливости ради следует отметить, что указанный механизм можно реализовать с помощью событий мыши, как показывает Илья Кантор в своем учебнике, однако мы будем использовать нативные средства, опираясь на спецификацию. Поддержка технологии: ![]() Превью: ![]() Наша задача состоит в следующем: реализовать список задач, состоящий из трех колонок: все задачи, задачи, находящиеся в процессе выполнения, завершенные задачи. Разумеется, приложение должно предусматривать возможность добавления и удаления задач. Кроме того, должна быть предусмотрена возможность произвольного расположения задач. Это одна из наиболее интересных частей туториала — отслеживание элемента, находящегося под перетаскиваемым, и определение того, где должен располагаться перетаскиваемый элемент, над или под отслеживаемым. Для стилизации будет использоваться Bootstrap. Если вам это интересно, прошу следовать за мной. Разметка: <head>
<!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous" /> <!-- custom CSS --> <link rel="stylesheet" href="style.css" /> </head> <body class="container"> <h1>Drag & Drop Example</h1> <main class="row"> <div class="input-group"> <div class="input-group-prepend"> <span class="input-group-text">Enter new todo: </span> </div> <input type="text" class="form-control" placeholder="todo4" data-name="todo-input" /> <div class="input-group-append"> <button class="btn btn-success" data-name="add-btn">Add</button> </div> </div> <div class="col-4"> <h3>Todos</h3> <ul class="list-group" data-name="todos-list"> <li class="list-group-item" data-id="1" draggable="true"> <p>todo1</p> <button class="btn btn-outline-danger btn-sm" data-name="remove-btn" > X </button> </li> <li class="list-group-item" data-id="2" draggable="true"> <p>todo2</p> <button class="btn btn-outline-danger btn-sm" data-name="remove-btn" > X </button> </li> <li class="list-group-item" data-id="3" draggable="true"> <p>todo3</p> <button class="btn btn-outline-danger btn-sm" data-name="remove-btn" > X </button> </li> </ul> </div> <div class="col-4"> <h3>In Progress</h3> <ul class="list-group" data-name="in-progress-list"></ul> </div> <div class="col-4"> <h3>Completed</h3> <ul class="list-group" data-name="completed-list"></ul> </div> </main> <!-- custom JS --> <script src="script.js"></script> </body> Здесь у нас имеется контейнер с полем для ввода текста задачи и кнопкой для ее добавления в список (input-group), а также три контейнера-колонки (list-group) для всех задач (todos-list), задач в процессе выполнения (in-progress-list) и завершенных задач (completed-list). Что касается атрибутов «data», то они предназначены для разделения стилизации и управления: классы — для стилизации, data — для управления. Стили: body {
min-height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; color: #222; } main { max-width: 600px; } .input-group { margin: 1rem; } .list-group { min-height: 100px; height: 100%; } .list-group-item { display: flex; justify-content: space-between; align-items: center; } div + div { border-right: 1px dotted #222; } h3 { text-align: center; } p { margin: 0; } .completed p { text-decoration: line-through; } .in-progress p { border-bottom: 1px dashed #222; } .drop { background: #eee; border-radius: 4px; } Классы «in-progress» и «completed» служат индикаторами нахождения задачи в соответствующей колонке. Класс «drop» предназначен для визуализации попадания задачи в зону для «бросания». Прежде чем переходить к скрипту, отметим, что нами будут использоваться далеко не все события перетаскивания, но большинство из основных. Определяем главный контейнер, в котором будет осуществляться поиск элементов и которому будет делегирована обработка событий: const main = document.querySelector("main");
Реализуем добавление и удаление задач через обработку клика: main.addEventListener("click", (e) => {
// нас интересует только нажатие кнопки if (e.target.tagName === "BUTTON") { // получаем название кнопки из атрибута "data-name" const { name } = e.target.dataset; // если перед нами кнопка для добавления задачи в список if (name === "add-btn") { // определяем поле для ввода текста задачи const todoInput = main.querySelector('[data-name="todo-input"]'); // если оно не является пустым if (todoInput.value.trim() !== "") { // получаем текст задачи const value = todoInput.value; // создаем шаблон задачи const template = ` <li class="list-group-item" draggable="true" data-id="${Date.now()}"> <p>${value}</p> <button class="btn btn-outline-danger btn-sm" data-name="remove-btn">X</button> </li> `; // находим список задач const todosList = main.querySelector('[data-name="todos-list"]'); // добавляем в него шаблон задачи todosList.insertAdjacentHTML("beforeend", template); // очищаем поле для ввода текста задачи todoInput.value = ""; } // если перед нами кнопка для удаления задачи } else if (name === "remove-btn") { // просто удаляем ее e.target.parentElement.remove(); } } }); Переходим непосредственно к перетаскиванию. Для начала реализуем попадание в зону для «бросание» и уход из нее посредством добавления/удаления соответствующего класса: main.addEventListener("dragenter", (e) => {
// нас интересуют только колонки if (e.target.classList.contains("list-group")) { e.target.classList.add("drop"); } }); main.addEventListener("dragleave", (e) => { if (e.target.classList.contains("drop")) { e.target.classList.remove("drop"); } }); Далее обрабатываем начало перетаскивания: main.addEventListener("dragstart", (e) => {
// нас интересует только задача if (e.target.classList.contains("list-group-item")) { // сохраняем идентификатор задачи в объекте "dataTransfer" в виде обычного текста; // dataTransfer также позволяет сохранять HTML - text/html, // но в данном случае нам это ни к чему e.dataTransfer.setData("text/plain", e.target.dataset.id); } }); Теперь нам нужно каким-то образом отслеживать элемент, находящийся под перетаскиваемым. Это необходимо для того, чтобы произвольно располагать задачи в списке, т.е. менять задачи в колонке местами. При обработке события «mousemove» для этого используется метод «elementFromPoint(x, y)». Прелесть рассматриваемого интерфейса состоит в том, что для определения «низлежащего» элемента нам достаточно обработать событие «dragover»: // создаем переменную для хранения "низлежащего" элемента
let elemBelow = ""; main.addEventListener("dragover", (e) => { // отключаем стандартное поведение браузера; // это необходимо сделать в любом случае e.preventDefault(); // записываем в переменную целевой элемент; // валидацию сделаем позже elemBelow = e.target; }); Наконец, обрабатываем событие «drop» («бросание»): main.addEventListener("drop", (e) => {
// находим перетаскиваемую задачу по идентификатору, записанному в dataTransfer const todo = main.querySelector( `[data-id="${e.dataTransfer.getData("text/plain")}"]` ); // прекращаем выполнение кода, если задача и элемент - одно и тоже if (elemBelow === todo) { return; } // если элементом является параграф или кнопка, значит, нам нужен их родительский элемент if (elemBelow.tagName === "P" || elemBelow.tagName === "BUTTON") { elemBelow = elemBelow.parentElement; } // на всякий случай еще раз проверяем, что имеем дело с задачей if (elemBelow.classList.contains("list-group-item")) { // нам нужно понять, куда помещать перетаскиваемый элемент: // до или после низлежащего; // для этого необходимо определить центр низлежащего элемента // и положение курсора относительно этого центра (выше или ниже) // определяем центр const center = elemBelow.getBoundingClientRect().y + elemBelow.getBoundingClientRect().height / 2; // если курсор находится ниже центра // значит, перетаскиваемый элемент должен быть помещен под низлежащим // иначе, перед ним if (e.clientY > center) { if (elemBelow.nextElementSibling !== null) { elemBelow = elemBelow.nextElementSibling; } else { return; } } elemBelow.parentElement.insertBefore(todo, elemBelow); // рокировка элементов может происходить в разных колонках // необходимо убедиться, что задачи будут визуально идентичными todo.className = elemBelow.className; } // если целью является колонка if (e.target.classList.contains("list-group")) { // просто добавляем в нее перетаскиваемый элемент // это приведет к автоматическому удалению элемента из "родной" колонки e.target.append(todo); // удаляем индикатор зоны для "бросания" if (e.target.classList.contains("drop")) { e.target.classList.remove("drop"); } // визуальное оформление задачи в зависимости от колонки, в которой она находится const { name } = e.target.dataset; if (name === "completed-list") { if (todo.classList.contains("in-progress")) { todo.classList.remove("in-progress"); } todo.classList.add("completed"); } else if (name === "in-progress-list") { if (todo.classList.contains("completed")) { todo.classList.remove("completed"); } todo.classList.add("in-progress"); } else { todo.className = "list-group-item"; } } }); Вот и все. Как видите, ничего сложного. Зато какие возможности по добавлению интерактивности на страницу. Осталось дождаться, когда мобильные браузеры реализуют данную технологию, и будет всем счастье. Надеюсь, вы нашли для себя что-нибудь интересное. Благодарю за внимание и хорошего дня. =========== Источник: habr.com =========== Похожие новости:
Программирование ), #_razrabotka_vebsajtov ( Разработка веб-сайтов ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 27-Апр 05:21
Часовой пояс: UTC + 5