Что такое драг энд дроп. Перетаскивание элементов (Drag and drop). Начало перетаскивания объекта

Что такое драг энд дроп. Перетаскивание элементов (Drag and drop). Начало перетаскивания объекта

30.10.2019

Известный факт, что поле для загрузки файлов трудно стилизовать так, как хочется разработчику. Многие просто скрывают его и добавляют кнопку, которая открывает диалог выбора файлов. Однако, теперь у нас появился даже ещё более модный способ обработки выбора файлов: drag and drop.

Технически это уже было возможно сделать, потому что большинство (если не все) реализации поля выбора файлов позволяли перетаскивать файлы для их выбора, но это требовало от вас показывать элемент с типом file . Итак, давайте по-настоящему использовать API, которое даёт нам браузер, для реализации выбора файлов через drag-and-drop и их загрузки на сервер.

В этой статье мы будем использовать чистый ES2015+ JavaScript (без фреймворков или библиотек) для выполнения этого проекта, и это предполагает, что у вас есть опыт работы с JavaScript в браузере. Этот пример - помимо ES2015+ синтаксиса, который можно легко изменить на синтаксис ES5 или транспилировать с помощью Babel - должен быть совместим со всеми вечнозелёными браузерами + IE 10 и 11.

Ниже можете видеть пример того, что должно получиться:

Демонстрационная страница, на которой можно загрузить файлы с помощью drag and drop с немедленным предварительным просмотром изображений и демонстрацией индикатора прогресса загрузки.

События Drag-and-Drop

Первое, что мы должны обсудить, это события связанные с перетаскиванием, потому что они движущая сила этого функционала. В общем, есть восемь событий, срабатывающих в браузере и связанных с перетаскиванием: drag , dragend , dragenter , dragexit , dragleave , dragover , dragstart и drop . Мы не будем проходиться по ним всем, потому что события drag , dragend , dragexit и dragstart срабатывают на элементе, который перетаскивают, а это не наш случай, мы будем перетаскивать файлы из нашей файловой системы вместо DOM-элементов, так что эти события никогда не сработают.

Если вам стало интересно почитать об этих событиях, то вы можете изучить документацию связанную с ними на MDN .

Как и следовало ожидать, вы можете навесить обработчики для нужных событий таким же образом, каким происходит регистрация обработчиков большинства браузерных событий: с помощью addEventListener .

Let dropArea = document.getElementById("drop-area")
dropArea.addEventListener("dragenter", handlerFunction, false)
dropArea.addEventListener("dragleave", handlerFunction, false)
dropArea.addEventListener("dragover", handlerFunction, false)
dropArea.addEventListener("drop", handlerFunction, false)

Ниже приведено небольшое описание того, что эти события делают. Используем dropArea из примера кода выше для того, чтобы сделать описание понятнее:

Событие dragenter  - перетаскиваемый объект перетаскивается над dropArea , делая dropArea целью события drop , если пользователь перетащит его туда. Событие dragleave - перетаскиваемый объект перетащили за пределы dropArea на другой элемент, делая его целью события drop вместо dropArea . Событие dragover срабатывает каждые несколько сотен миллисекунд, пока объект перетаскивают над dropArea . Событие drop  - пользователь отпустил кнопку мыши, перетаскиваемый объект перетащили на dropArea .

Стоит отметить, что при перетаскивании объекта над элементом, являющимся дочерним для dropArea , событие dragleave сработает над dropArea , а событие dragenter на дочернем элементе, потому что он становится target . Событие drop всплывёт до элемента dropArea (конечно, если до этого всплытие не остановит другой обработчик событий), таким образом событие сработает на dropArea , несмотря на то, что target у него будет другим.

Также обратите внимание, что для реализации пользовательского интерфейса с drag-and-drop, вам необходимо вызывать event.preventDefault() на каждом из слушателей этих событий. Если вы этого не сделаете, то браузер в конечном итоге откроет файл, который вы перетаскиваете, вместо того, чтобы отправить его в обработчик события drop .

Настраиваем нашу форму

До того как мы начнём добавлять функциональность drag-and-drop, нам надо добавить базовую форму со стандартным полем типа file . Технически это не обязательно, но рекомендуется предоставить такую альтернативу пользователям, чей браузер не поддерживает drag-and-drop API.



Загрузите изображения с помощью диалога выбора файлов или перетащив нужные изображения в выделенную область





Довольно простая структура. Вы можете заметить обработчик события onchange на input . Посмотрим на него позже. Было бы также хорошей идеей добавить action к тегу form и кнопку submit , чтобы помочь людям, у которых выключен JavaScript. Затем можно использовать JavaScript для того, чтобы избавиться от них, почистить форму. В любом случае, вам понадобится серверный скрипт для загрузки файлов, неважно написан ли он собственными силами или вы используете сервис, такой как Cloudinary . Кроме этого, здесь нет ничего особенного, так что давайте набросаем стили:

#drop-area {
border: 2px dashed #ccc;
border-radius: 20px;
width: 480px;
font-family: sans-serif;
margin: 100px auto;
padding: 20px;
}
#drop-area.highlight {
border-color: purple;
}
p {
margin-top: 0;
}
.my-form {
margin-bottom: 10px;
}
#gallery {
margin-top: 10px;
}
#gallery img {
width: 150px;
margin-bottom: 10px;
margin-right: 10px;
vertical-align: middle;
}
.button {
display: inline-block;
padding: 10px;
background: #ccc;
cursor: pointer;
border-radius: 5px;
border: 1px solid #ccc;
}
.button:hover {
background: #ddd;
}
#fileElem {
display: none;
}

Многие из этих стилей пока не используются, но это нормально. Основным моментом является то, что поле file скрыто, а его подпись label стилизована так, чтобы выглядеть как кнопка, таким образом люди поймут, что кликнув по нему вызовется диалог выбора файлов. Кроме того, мы следуем соглашению, согласно которому область, куда следует перетащить файл, обозначается пунктирной линией.

Добавляем функциональность Drag-and-Drop

Теперь можем перейти к сладкому: drag and drop. Давайте напишем скрипт внизу страницы или в отдельном файле, смотря как вам больше нравится. Первое, что нам понадобится - это ссылка на область, куда предстоит тащить файл. Так мы сможем обрабатывать нужные нам события на ней:

Let dropArea = document.getElementById("drop-area")

Теперь давайте добавим сами события. Начнём с добавления обработчиков для всех событий, чтобы предотвратить поведение по умолчанию и остановить всплытие выше необходимого:

;["dragenter", "dragover", "dragleave", "drop"].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false)
}) function preventDefaults (e) {
e.preventDefault()
e.stopPropagation()
}

Теперь давайте добавим индикатор, который позволит пользователям понять, что они действительно перетаскивали элементы над нужной областью, используем CSS для изменения цвета границы области для перетаскивания. Стили уже описаны выше для селектора #drop-area.highlight , так что давайте используем JavaScript для добавления и удаления класса highlight , когда это необходимо.

;["dragenter", "dragover"].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false)
}) ;["dragleave", "drop"].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false)
}) function highlight(e) {
dropArea.classList.add("highlight")
} function unhighlight(e) {
dropArea.classList.remove("highlight")
}

Мы использовали оба события dragenter и dragover для подсвечивания области для перетаскивания по причинам, о которых я говорил ранее. Если вы начинаете перетаскивать непосредственно над dropArea и затем перешли на дочерний элемент, то сработает событие dragleave и подсвечивание области пропадёт. Событие dragover сработает после событий dragenter и dragleave , так что подсветка вернётся обратно на dropArea до того, как мы увидим, что она пропала.

Мы также убираем подсветку, когда перетаскиваемый элемент покидает обозначенную область и когда его перетаскивают в неё.

Теперь всё что нам нужно, это выяснить что делать, когда файлы будут перетащены:

DropArea.addEventListener("drop", handleDrop, false) function handleDrop(e) {
let dt = e.dataTransfer
let files = dt.files handleFiles(files)
}

Код выше не приближает нас к цели, но делает две важные вещи:

  1. Демонстрирует, как получить данные о файлах, которые перетащили.
  2. Приводит нас в то же место, что и поле input с типом file и обработчиком на событие onchange: handleFiles .

Помните о том, что files это не массив, а FileList . Таким образом, при реализации handleFiles , нам нужно преобразовать FileList в массив, чтобы более легко было его итерировать:


([...files]).forEach(uploadFile)
}

Это было скучно (That was aniticlimactic ). Перейдём к uploadFile , где будут действительно крутые штуки (real meaty stuff ).

Function uploadFile(file) {

method: "POST",
body: formData
})
.then(() => { /* Готово. Информируем пользователя */ })
.catch(() => { /* Ошибка. Информируем пользователя */ })
}

Здесь мы используем FormData  - встроенный браузерный API для создания форм с данными для отправки на сервер. Для этого мы используем fetch API, чтобы действительно отправить изображения на сервер. Убедитесь, что вы изменили URL для работы с вашим сервером или сервисом, с помощью formData.append можете добавить к форме любые дополнительные данные, которые могут потребоваться для работы с вашим сервером. Как альтернатива, если вы хотите поддерживать Internet Explorer, вы можете захотеть использовать XMLHttpRequest , это значит, что ваш uploadFile будет выглядеть так:

Function uploadFile(file) {

xhr.open("POST", url, true) xhr.addEventListener("readystatechange", function(e) {
// Готово. Информируем пользователя
}
// Ошибка. Информируем пользователя
}
xhr.send(formData)
}

В зависимости от настроек вашего сервера, вы можете проверять различные значения status , отличные от 200 , но для наших нужд это сработает.

Дополнительные возможности

Это вся базовая функциональность, но часто требуется её расширить. Конкретно в этом руководстве, мы добавим панель предпросмотра, где будут показаны выбранные изображения, также добавим индикатор прогресса, который будет показывать пользователю статус загрузки. Итак, давайте начнём с предпросмотра изображений.

Предпросмотр изображений

Есть несколько способов сделать это: вы можете ждать пока изображения загрузятся и запросить у сервера URL для картинок, но это означает что вам придётся ждать пока выполняется загрузка, а временами изображения могут быть довольно большими. Альтернатива, которую мы будет исследовать сегодня - это использовать FileReader API с данными файлов, которые мы получили из события drop . Это работает асинхронно, но вы можете использовать синхронную альтернативу FileReaderSync , но пользователи могут попробовать прочитать несколько больших файлов подряд, таким образом, это может заблокировать поток выполнения на длительное время и по-настоящему испортить впечатления пользователя от сервиса. Что же, давайте создадим функцию previewFile и посмотрим как это работает:

Function previewFile(file) {
let reader = new FileReader()
reader.readAsDataURL(file)
reader.onloadend = function() {
let img = document.createElement("img")
img.src = reader.result
document.getElementById("gallery").appendChild(img)
}
}

Здесь мы создали new FileReader и вызвали метод readAsDataURL для объекта File . Как уже упоминалось, это работает асинхронно, поэтому нужно добавить обработчик события onloadend для обработки результата чтения файла. После этого используем base64 URL для атрибута src нового элемента и добавляем его в элемент gallery . Есть только две вещи, которые надо сделать, чтобы всё было готово и работало: добавить элемент gallery и вызов функции previewFile .

Во-первых, добавим HTML, который приведен ниже, сразу после закрывающего тега form:

Ничего особенного, это просто div . Стили уже заданы для него и изображений в нём, так что больше здесь ничего делать не надо. Теперь изменим функцию handleFiles на следующую:

Function handleFiles(files) {
files = [...files]
files.forEach(uploadFile)
files.forEach(previewFile)
}

Есть несколько способов сделать это, например композиция или простой колбэк forEach , в котором запускается uploadFile и previewFile , и это тоже сработает. Таким образом, когда вы перетащите или выбираете несколько изображений, они будут показаны почти мгновенно ниже формы. Интересная мысль по этому поводу: в некоторых приложениях вы можете не захотеть действительно загружать изображения на сервер, а вместо этого хранить ссылки на них в localStorage или в каком-нибудь другом кеше на стороне пользователя, чтобы приложение имело к ним доступ позже. Я лично не могу придумать хорошие сценарии использования этого, но я готов поспорить, что такие есть.

Примечание переводчика: от себя добавлю, что для реализации предпросмотра неграфических файлов, было бы неплохо подготовить изображения-заглушки, которые будут показаны при добавлении таких файлов. Такая картинка в дальнейшем может быть заменена другой, которую подготовит сервер уже после загрузки файла.

Отслеживание прогресса

Если что-нибудь занимает некоторое время, индикатор прогресса помогает пользователю понять, что процесс идёт, и показывает, как долго это что-нибудь будет выполняться. Добавить индикатора прогресса довольно легко благодаря HTML5 тегу progress . Давайте начнём с добавления его в HTML-код.

Вы можете вставить его сразу после элемента label или между элементами form и div для предпросмотра изображений, как больше нравится. Вообще, вы можете добавить его куда захотите в пределах тега body . Стили для этого примера не добавлены, так что будет отрисован браузерный элемент по умолчанию, который вполне пригоден. Теперь давайте добавим JavaScript. Сначала рассмотрим реализацию с использованием fetch , а затем покажем версию для XMLHttpRequest . Для начала нам понадобится пара новых переменных в верхней части скрипта:

Let filesDone = 0
let filesToDo = 0
let progressBar = document.getElementById("progress-bar")

При использовании fetch мы только можем определить, когда загрузка завершена, так что единственная информация, которую мы отслеживаем: сколько файлов выбрано для загрузки (переменная filesToDo) и количество уже загруженных файлов (переменная filesDone). Также мы храним ссылку на элемент #progress-bar , чтобы мы могли быстро обновлять его. Теперь давайте создадим пару функций для управления прогрессом:

Function initializeProgress(numfiles) {
progressBar.value = 0
filesDone = 0
filesToDo = numfiles
} function progressDone() {
filesDone++
progressBar.value = filesDone / filesToDo * 100
}

Когда мы только начинаем загрузку, вызовем функцию initializeProgress для сброса состояния индикатора. Затем, с каждой выполненой загрузкой, мы вызываем функцию progressDone для увеличения числа загруженых файлов на единицу и обновления индикатора для демонстрации прогресса. Итак, добавим вызовы этих функций, обновив пару старых:

Function handleFiles(files) {
files = [...files]
initializeProgress(files.length) // <- Добавили эту строку
files.forEach(uploadFile)
files.forEach(previewFile)
} function uploadFile(file) {
let url = "ВАШ URL ДЛЯ ЗАГРУЗКИ ФАЙЛОВ"
let formData = new FormData() formData.append("file", file) fetch(url, {
method: "POST",
body: formData
})
.then(progressDone) // <- Добавим вызов `progressDone` здесь
.catch(() => { /* Ошибка. Сообщаем пользователю */ })
}

И на этом всё. Теперь пришло время посмотреть как будет выглядеть реализация с XMLHttpRequest . Мы могли бы просто сделать быстрое обновление в uploadFile , но XMLHttpRequest фактически даёт нам больше возможностей чем fetch , а именно: мы можем добавить обработчик события для отслеживания прогресса загрузки на каждом запросе, который будет периодически давать информацию о прогрессе. Исходя из этого, нам нужно отслеживать процентную готовность каждого запроса вместо количества выполненных запросов. Итак, давайте начнём с замены объявлений переменных filesDone и filesToDo на следующий код:

Let uploadProgress =

Тогда нам нужно обновить и наши функции. Переименуем progressDone в updateProgress и изменим её код как показано ниже:

Function initializeProgress(numFiles) {
progressBar.value = 0
uploadProgress = for(let i = numFiles; i > 0; i--) {
uploadProgress.push(0)
}
} function updateProgress(fileNumber, percent) {
uploadProgress = percent
let total = uploadProgress.reduce((tot, curr) => tot + curr, 0) / uploadProgress.length
progressBar.value = total
}

Теперь initializeProgress инициализирует массив с длиной, равной numFiles , который заполнен нулями, означающими, что каждый файл загружен на 0%. В updateProgress мы видим какое из изображений обновляет свой прогресс и изменяем значение элемента с нужным индексом на предоставленный percent . Затем мы вычисляем общий процент выполнения как среднее среди всех процентов и обновляем индикатор прогресса, чтобы отобразить вычисленное значение. Мы по-прежнему вызываем initializeProgress в handleFiles также, как делали это в примере с fetch . Таким образом, всё что нам нужно, это обновить uploadFile , добавив вызов updateProgress .

Function uploadFile(file, i) { // <- Добавили параметр `i`
var url = "ВАШ URL ДЛЯ ЗАГРУЗКИ ФАЙЛОВ"
var xhr = new XMLHttpRequest()
var formData = new FormData()
xhr.open("POST", url, true) // Добавили следующие слушатели
xhr.upload.addEventListener("progress", function(e) {
updateProgress(i, (e.loaded * 100.0 / e.total) || 100)
}) xhr.addEventListener("readystatechange", function(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
// Готово. Сообщаем пользователю
}
else if (xhr.readyState == 4 && xhr.status != 200) {
// Ошибка. Сообщаем пользователю
}
}) formData.append("file", file)
xhr.send(formData)
}

Первое, что нужно отметить, это то, что мы добавили параметр i . Это индекс файла в списке файлов. Нам не нужно обновлять handleFiles для передачи этого параметра, потому что он использует forEach , который уже передаёт индекс элемента вторым параметром в колбэк. Также мы добавили слушатель события progress в xhr.upload , чтобы можно было вызвать updateProgress со значением прогресса. Объект события (e в нашем коде) имеет два информативных поля: loaded  - количество уже загруженных байтов, и total  - общее количество байтов.

Выражение || 100 нужно потому, что иногда, при возникновении ошибки, e.loaded и e.total будут равны нулю, что значит, что вычисления с ними дадут NaN , таким образом 100 используется вместо отчёта о выполнении загрузки. Вы можете также использовать 0 . В любом случае ошибки будут отображаться в обработчике события readystatechange , и вы можете сообщить о них пользователю. Это сделано просто для предотвращения исключений, связанных с попытками вычислений с NaN .

Примечание редактора: Такой подход к подавлению ошибок не очень правильный, и, к тому же, приводит к скачкам индикатора загрузки. Гораздо надёжнее заранее проверить значения на корректность и в случае ошибки не менять значение индикатора загрузки.

Заключение

Это последняя часть. Теперь у нас есть страница, на которой можно загружать изображения с помощью drag and drop, есть мгновенный предпросмотр изображений и отображение прогресса загрузки с помощью индикатора. Вы можете посмотреть окончательную версию (с использованием XMLHttpRequest) в действии на CodePen , но помните, что сервис, через который я загружаю файлы, имеет ограничения, и если много людей будут его использовать одновременно, то он может быть недоступен некоторое время.

Проще всего что-то взять и положить, чем писать, что нужно взять и куда положить. Конечно без мышки, или подобного ей устройства, ничего не выберешь и ничего не укажешь, но даже в текущем положении вещей использование идеи «drag and drop» очень естественно и комфортно.

Область применения идеи - далеко не только интернет-магазины, электронные библиотеки, поисковые или информационные системы, а и прикладная сфера. Идея очень даже применима в разработке сайтов и их элементов, создаваемых и сопровождаемых интерактивно, без участия программиста.

Описание идеи

Выбрать, переместить и положить - идея естественная и удобная. Просто удивительно, что она не родилась, когда мышка стала незаменимым аксессуаром компьютера.

Самый очевидный пример - выбор товара в интернет-магазине. Взять мышкой нужный товар и перетащить его в корзину для покупок - просто, естественно и удобно. Загрузка файлов: взять документ за пределами окна браузера и положить его на элемент страницы, инициировав тем самым передачу документа на сервер - также практичная идея.

Для разработчика идея «drag and drop» - это манипулирование элементами страницы без ручного пересчета координат и размеров тегов, возможность выбора нескольких элементов и выравнивания их, а также перемещения сторон блочных тегов.

HTML и CSS - отличные языки описания тегов и стилей их оформления, но когда разработчик имеет возможность интерактивно манипулировать элементами страницы без ручного пересчета координат и размеров, это делает работу более комфортной и эффективной.

Простой перенос файлов

«Drag and drop»: перевод с английского на русский буквально звучит «тащи и бросай». На практике это звучит и действует лучше: выбрал, перенес и отпустил - просто и естественно.

Реализовать на странице передачу файлов на страницу, на сервер или для иного использования очень просто.

В данном примере мышкой было выбрано несколько файлов на рабочем столе (левый рисунок). На выделении была нажата левая кнопка мышки и выбранное «поехало» в корзинку. Браузер сам показал, как это происходит, написал подсказку «копирование» и вокруг создал контуры перемещаемых файлов.

Когда мышка оказалась над корзинкой, посетитель отпустил левую кнопку мышки, событие «тащи и бросай» состоялось и на странице сайта (нижний рисунок) JavaScript-код смог получить и обработать все файлы, которые посетитель предоставил странице (сайту).

Описание реализации

Код, который выполняет эту процедуру, очень прост. Его повторить в любых вариантах использования может даже начинающий разработчик.

Здесь интерфейс пользователя представлен двумя тегами: scPlaceFile (это сама корзинка, куда нужно положить файлы) и scPlaceFiles (это результат обработки файлов, в данном случае их список).

Логика работы страницы такова. При загрузке страницы в браузере в корзинке назначается обработчик события «ondrop» - положить, остальные события блокируются и не используются.

Страница работает в штатном режиме, но как только посетитель выберет файл (файлы) и перетащит их на картинку корзинки, то есть на тег scPlaceFile, будет запущена обработка события «файлы приехали».

Данный обработчик просто выводит список файлов. Их количество находится в event.dataTransfer.files.length, а информация о каждом файле в event.dataTransfer.files[i].name. Что делать с полученными данными, определяет разработчик, в данном случае просто формируется список полученных файлов.

После обработки событие блокируется и не распространяется. Это необходимо, чтобы браузер не занимался самодеятельностью и не вмешивался в процесс обработки полученной информации.

DnD и внешние данные

Загрузка изображений на сервер в «drag and drop» - обычная практика применения этой технологии. Как правило, разработчик создает форму для загрузки файла (1), которая работает обычным образом (2). Посетитель может в обычном режиме выбирать файлы и загружать их.

Однако если посетитель на определенное место формы сделает «drag and drop», то поле имени файла (файлов) заполнится автоматически.

Это хорошее решение. Допустить, что на компьютере нет мышки, конечно, очень трудно. Но лучше разработать интерфейс пользователя в обычном варианте и в DnD-реализации.

DnD и внутренние данные

Забота об интересах посетителя всегда важна, но проблемы разработчика также имеют значение. Реализовать «drag and drop» можно не только стандартными средствами, но и посредством обработки событий мышки на элементах страницы.

Задача расчета значений координат тегов и их размеров возникает постоянно. Ручной расчет - хорошая практика, но интерактивный вариант более удобен. Все теги всегда имеют прямоугольную форму и, отслеживая «мышиные» события на сторонах элементов, можно создать возможность автоматического перемещения элементов в нужное место страницы, либо изменения их.

Обработка события нажатия кнопки мышки - запоминание координат места клика, например, одной из сторон элемента. Перемещение мышки - сторона перемещается в нужном направлении. Отпускание кнопки мышки - сторона останавливается, а ее координаты изменяются. Так можно изменить положение элемента или его размер.

Формально это не «drag and drop», но эффект аналогичный и практичный. Сделав универсальные обработчики для любого элемента страницы, можно получить хороший интерактивный результат, ускорить разработку и упростить код.

Визуальное и ручное программирование

Мышка на компьютере и пальцы на смартфоне - совершенно разные подходы к реализации интерфейса пользователя (посетитель, разработчик). Является вполне естественным и современным требование кроссбраузерности.

Все это в совокупности усложняет создание страниц, но применяя идею «drag and drop» в ее стандартной форме, используя ее события, совмещая эту идею с обычными событиями на элементах, можно реализовать механизм, при котором создание страницы будет происходить визуально.

Сейчас давайте рассмотрим выбор элемента или элементов. Факт выбора - появление контекстного меню, например, цель - выравнивание выбранного (слева, справа, по центру), или распределение элементов по вертикали или горизонтали с одинаковым шагом, или изменение их размеров (по минимальному, по максимальному).

Автоматический перерасчет координат и размеров предпочтительнее ручного. Меньше ошибок - быстрее достигается цель. Кроме того, можно сделать станицу в одном браузере, сохранить положение и размеры элементов. Открыв эту страницу на смартфоне, можно поправить значения координат и размеров и запомнить их для конкретной модели смартфона или версии браузера.

Так одна и та же страница без ручного соблюдения требования кроссбраузерности будет иметь различные данные для отображения на различных устройствах и в различных браузерах.

Если позволить посетителю выполнять эти процедуры самостоятельно, а также выбирать нужные элементы страницы из числа предусмотренных разработчиком, можно обеспечить кроссбраузерность и требуемую функциональность страницы с учетом мнения пользователя.

HTML Drag and Drop interfaces enable applications to use drag and drop features in Firefox and other browsers. For example, with these features, the user can select draggable elements with a mouse, drag the elements to a droppable element, and drop those elements by releasing the mouse button. A translucent representation of the draggable elements follows the mouse pointer during the drag operation.

For web sites, extensions, and XUL applications, you can customize the types of elements that can become draggable elements, and the type of feedback the draggable elements produce, and the droppable elements.

This document is an overview of HTML drag and drop. It includes a description of the interfaces, the basic steps to add drag and drop support to an application and a summary of the interoperability of the interfaces.

Drag Events

The basics

This section provides a summary of the basic steps to add drag and drop functionality to an application. Each section includes a description of the step, a short code example, and links to additional information.

Identify what is draggable

To make an element draggable requires adding the draggable attribute plus the ondragstart global event handler, as shown in the following code sample

Function dragstart_handler(ev) { console.log("dragStart"); // Add the target element"s id to the data transfer object ev.dataTransfer.setData("text/plain", ev.target.id); }

This element is draggable.

Define the drag image

By default, the browser supplies an image that appears beside the mouse pointer during a drag operation. However, an application may define a custom image by using the element but it can also be a or any other image element."> setDragImage() method as shown in the following example.

Function dragstart_handler(ev) { // Create an image and then use it for the drag image. // NOTE: change "example.gif" to an existing image or the image // will not be created and the default drag image will be used. var img = new Image(); img.src = "example.gif"; ev.dataTransfer.setDragImage(img, 10, 10); }

Задумывался ли ты, какой десктоп используют прожженные линуксоиды, которые большую часть времени сидят в терминале, а о таких вещах, как иконки на рабочем столе и drag’n’drop, либо уже забыли, либо никогда не узнавали? Те самые странные люди, которые на каждый чих пишут shell-скрипт и с отвращением смотрят на KDE и GNOME. Что ж, сегодня мы покажем, как настроить настоящий гиковский десктоп с тайлами, строками состояния и терминалами.

Начнем с выбора дистрибутива. Ubuntu, Mint, Debian и Fedora не годятся на роль дистрибутива для истинного гика. В них нет ничего плохого, но если ты обратишь внимание, что же юзают любители ковырять систему, то, скорее всего, увидишь Arch Linux, Gentoo или Slackware. Это простые как сапог дистрибутивы, позволяющие тонко контролировать систему.

Остановимся на Arch Linux, наиболее популярном сегодня дистрибутиве для гиков. Свежеустановленный Arch Linux представляет собой простенький консольный дистрибутив, который ничего не умеет. Наиболее консервативным товарищам, освоившим UNIX еще в восьмидесятых, хватит и этого, но все-таки нужен хоть какой-то графический интерфейс и графический браузер (да, есть и текстовые, например Lynx, w3m, Links, ELinks).

В UNIX-подобных системах (к которым, конечно же, относится и Linux) за формирование графического интерфейса в первую очередь отвечает так называемый менеджер окон (Window Manager, WM). Это особое приложение, основная задача которого - управлять окнами, создавать для них рамки с кнопками, корректно обрабатывать ситуации наложения окон друг на друга, а в отдельных случаях создавать панельки и менюшки для запуска приложений и управления ими; хотя эта задача обычно ложится на плечи других приложений, которые вместе с менеджером окон формируют полноценный рабочий стол (Desktop Environment, DE), такой как GNOME, KDE или Xfce.

Нам никакой DE не нужен, это изобретение шайтанов, пришедших в мир UNIX из Windows. Более того, нам не нужен классический менеджер окон, ибо двигать окна мышкой и накладывать их друг на друга контрпродуктивно. Вместо этого мы установим так называемый тайловый менеджер окон, который не разрешает таскать окна по экрану, а вместо этого либо всегда открывает все окна на полный экран, либо делит экран между окнами пропорционально (или непропорционально), не позволяя им накладываться друг на друга.

Наверное, лучший тайловый менеджер окон из доступных на сегодняшний день - это i3. Установим его:

$ sudo pacman -S i3-wm

Не отходя от кассы, сразу поставим i3status (он будет выводить статусную строку с различной полезной информацией и треем в нижней части экрана), а также крохотное приложение dmenu, позволяющее быстро запускать приложения, и, конечно же, эмулятор терминала:

$ sudo pacman -S i3status dmenu xterm

Если это действительно чистая установка Arch Linux, то также понадобится сервер Xorg, драйвер видеокарты и драйвер тачпада (если это ноутбук):

$ sudo pacman -S xorg-server xf86-video-intel xf86-input-synaptics

Пакет xf86-video-intel меняем на нужный в зависимости от производителя видеочипа:

  • Nvidia: xf86-video-nouveau;
  • ATI/AMD: xf86-video-ati.

Нам понадобится также менеджер логина - то самое графическое окно с полями для ввода имени пользователя и пароля, которое ты видишь при загрузке системы. Никто не запрещает использовать менеджер логина KDE, GNOME, Xfce или любой другой графической среды, но это весьма тяжеловесные приложения, которые тащат за собой массу совершенно не нужных зависимостей. Поэтому мы остановимся на простом и быстром менеджере логина SLiM:

$ sudo pacman -S slim

Сперва подправим его конфиг /etc/slim.conf , добавив в него такую строчку:

Current_theme archlinux

Сразу после успешного логина SLiM будет исполнять содержимое файла ~/.xsession , который по задумке должен делать некоторую первоначальную настройку пользовательского окружения и запускать менеджер окон. Не будем изменять традициям и добавим в файл следующие строки:

# Укажем предпочтительный язык для интерфейса системы и приложений # Удали эти строки, если предпочитаешь английский язык export LANG=ru_RU.utf8 export LC_ALL=ru_RU.utf8 # Настроим переключатель раскладки между русским и английским по клавише Caps Lock setxkbmap "us,ru" ",winkeys" "grp:caps_toggle,grp_led:caps" # Отключим раздражающий бибикающий звук xset b off # Запустим i3 exec i3

Это все, теперь можно запустить графический интерфейс:

$ sudo systemctl start slim

На экране должно появиться окно логина SLiM.


i3 и dmenu

Итак, перед тобой i3. Точнее, ты должен увидеть только черный экран со строкой состояния снизу (в ней отображается текущая заполненность диска, подключенные сетевые интерфейсы, уровень батареи, дата и время). Никаких меню, кнопок «Пуск» и прочего булшита.

i3 полностью управляется с клавиатуры. Win + Enter запускает терминал (сейчас это убогий xterm, но мы его заменим). По умолчанию окна занимают всю площадь экрана, деля его на две, три, четыре и более части по вертикали. Такое поведение можно отключить, нажав Win + w, тогда каждое окно будет открыто на полный экран - это практически идеальный вариант для ноутбуков с диагональю экрана 10–13".

Переключение между окнами - Win + стрелки; Win + 1…0 - переключение между рабочими столами, которые здесь называются воркспейсами (Workspace). Чтобы закрыть окно, нажимаем Win + Shift + Q. На данном этапе этих комбинаций будет вполне достаточно.

Win + d - особая комбинация. Она открывает dmenu - строку ввода вверху экрана, позволяющую быстро запускать нужные приложения. По своей сути dmenu сильно напоминает универсальную строку поиска типа Spotlight в macOS: ты просто начинаешь вбивать имя приложения, и он подсказывает тебе варианты. Автодополнение также работает. В i3 dmenu можно назвать аналогом меню приложений в классических рабочих столах.

i3 поддерживает массу вариантов деления экрана на рабочие области, так, чтобы в них можно было расположить различные приложения. Доступен и режим классических плавающих окон. Перевести текущее приложение в этот режим можно с помощью комбинации Win + Shift + пробел. Перетаскивать окно можно мышкой, зажав клавишу Win, изменять размер - правой кнопкой мыши, опять же с зажатой кнопкой Win.

Шрифты, терминал и scratchpad

По умолчанию i3 использует довольно унылый стандартный шрифт Xorg, а в качестве эмулятора терминала мы установили дурной xterm, который давно желают закопать не только пользователи, но и сами разработчики (я не шучу). Это необходимо исправить.

Начнем со шрифта. О выборе консольного моноширинного шрифта (а именно такой нам нужен, в том числе и для i3) много спорят, но я предлагаю остановиться на Terminus. Это четкий шрифт без засечек с минимумом округлых форм, он очень хорошо читается и отлично подходит для не Retina-дисплеев (да и для Retina тоже). Устанавливаем:

$ sudo pacman -S terminus-font

С эмулятором терминала тоже не все однозначно. Эталоном уже на протяжении многих лет считается urxvt (rxvt-unicode). Он легкий, быстрый и хорошо настраиваемый. Но так как в рамках этой статьи мы не можем вдаваться в детали глубокой настройки каждого компонента (а без такой настройки urxvt ужасен), то остановимся на LilyTerm. Он быстрый, имеет минимум зависимостей и все преимущества urxvt, а настроить его можно с помощью контекстного меню:

$ sudo pacman -S lilyterm

Теперь необходимо внести изменения в файл настроек i3, чтобы он подхватил наш шрифт и использовал LilyTerm вместо стандартного терминала. Открываем файл ~/.config/i3/config , находим опцию font и меняем ее значение:

Font pango:xos4 Terminus 8

Чтобы запускать LilyTerm по Win + Enter, добавляем такую опцию:

Bindsym $mod+Return exec lilyterm

Ну и опция для запуска dmenu с нашим шрифтом:

Bindsym $mod+d exec dmenu_run -fn "xos4 Terminus-8"

Плюс парочка строк для включения так называемого scratchpad:

Bindsym $mod+Shift+minus move scratchpad bindsym $mod+minus scratchpad show

Scratchpad в терминологии i3 - это доступное по хоткею небольшое окно посередине экрана. Он хорошо подходит для приложений, которые нужны часто, но отвлекают, постоянно находясь на экране (да, для мессенджеров). Win + минус показывает scratchpad, повторное нажатие скрывает его. Win + Shift + минус отправляет текущее окно в scratchpad.

Когда все изменения будут сделаны, сохраняем файл и нажимаем Win + Shift + R, чтобы перезапустить i3.

Продолжение доступно только подписчикам

Вариант 1. Оформи подписку на «Хакер», чтобы читать все материалы на сайте

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов.

Уже в течение долгого времени существуют JavaScript функции, которые позволяют нам создавать drag & drop интерфейсы. Но ни одна из этих реализаций, не является родной для браузера.У HTML5 есть собственный метод создания drag & drop интерфейсов (с небольшой помощью JavaScript). В этой статье, мы расскажем вам, как этого можно достичь …

Поддержка браузеров

В настоящее время HTML5 drag & drop поддерживается всеми основными браузерами для десктопов (в том числе IE (даже в IE 5.5 имеется частичная поддержка)), но не поддерживается ни одним из популярных мобильных браузеров.

Drag&Drop события

На каждом этапе перетаскивания (drag & drop) запускаются различные события для того, чтобы браузер знал, какой JavaScript код нужно выполнять. Список событий:

  • dragStart: запускается, когда пользователь начинает перетаскивать элементы;
  • dragEnter: запускается, когда перетаскиваемый элемент впервые перетаскивается над целевым элементом;
  • dragOver: срабатывает при перемещении мыши над элементом, когда происходит перетаскивание;
  • dragLeave: запускается, если курсор пользователя оставляет элемент при перетаскивании;
  • drag: приходит в действие каждый раз, когда мы двигаем мышью во время перетаскивания нашего элемента;
  • drop: запускается, когда выполняется фактический drop;
  • dragEnd: срабатывает, когда пользователь отпускает кнопку мыши при перетаскивании объекта.

С учетом всех этих слушателей событий, у вас есть хороший контроль над тем, как будет работать интерфейс.

Объект dataTransfer

Именно здесь и происходит все drag&drop волшебство. Этот объект содержит данные, которые были отправлены drag операцией. Данные могут быть установлены и получены различными способами, наиболее важными из которых являются:

  • dataTransfer.effectAllowed=value: возвращает разрешенные типы действий, возможные значения: none, copy, copyLink, copyMove, link, linkMove, move, all и uninitialized.
  • dataTransfer.setData(format, data): добавляет определенные данные и формат.
  • dataTransfer.clearData(format): очищает все данные для определенного формата.
  • dataTransfer.setDragImage(element, x, y): задает изображение, которое вы хотите перетащить, х и у значения указывают, где должен быть курсор мыши(0, 0 расположит его вверху слева).
  • data = dataTransfer.getData(format) : как следует из названия, он возвращает данные для определенного формата.

Создание drag&drop примера

Теперь мы начнем создавать простой drag&drop пример . Как вы видите, у нас есть два маленьких divs и один большой, мы можем перетащить маленькие дивы внутрь большого, и даже переместить их обратно.

Перетаскивание объекта

Первое, что нам нужно сделать, это создать HTML. Мы делаем div перетаскиваемыми с помощью draggable атрибута:

Когда это будет сделано, мы должны определить JavaScript функцию, которая будет cрабатывать, как только мы будем начинать передвигать этот элемент:

Function dragStart(ev) { ev.dataTransfer.effectAllowed="move"; ev.dataTransfer.setData("Text", ev.target.getAttribute("id")); ev.dataTransfer.setDragImage(ev.target,100,100); return true; }

В этом коде, мы сначала объявляем, какой тип эффекта позволяем в операции и устанавливаем его на move. Во второй строке, мы устанавливаем данные для работы, где текст будет Text и значением будет ID элемента, который мы перетаскиваем. После этого, мы используем метод setDragImage, который установит, что мы будем перетаскивать, а затем, где будет курсор во время перетаскивания, а так как, кубики 200 на 200 пикселей, мы поместили его в самый центр. В конце, мы возвращаем return true.

Drop объекта

Для того, чтобы элемент принял drop, он должен прослушать 3 различных события: dragEnter, dragOver, а также drop события. Так что давайте добавим это к нашему HTML5 в div с ID большого (big):

Function dragEnter(ev) { ev.preventDefault(); return true; } function dragOver(ev) { ev.preventDefault(); }

В первой функции мы определяем, что должно произойти, когда элемент, который мы перетаскиваем достигнет требуемого элемента, где должен произойти drop, в данном случае мы только предотвращаем поведение браузера по умолчанию. Следующее, в dragOver функции мы просто не допускаем, чтобы произошел drop по умолчанию.

В следующей части, мы определяем функцию, для того, когда элемент будет “брошен” на желаемой цели:

Function dragDrop(ev) { var data = ev.dataTransfer.getData("Text"); ev.target.appendChild(document.getElementById(data)); ev.stopPropagation(); return false; }

В этой последней части, мы сначала установили переменную data, где мы получаем все данные, которые доступны для текстового формата, а затем мы добавляем data к div, где нужно “бросить” элемент.

Сделать секцию drop целью

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

И это все, что нужно для того, чтобы позволить перетаскивание div на прежнее место.

Существует много drag&drop приложений, которые построены с использованием JavaScript библиотек, и зачастую использовать их проще. Но мы надеемся, что в этой HTML5 и JavaScript технике, вы увидете будущий потенциал для решения ваших задач.



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows