- HTML5 Drag’n’Drop
- Основная логика Drag’n’Drop
- Отмена переноса браузера
mousemoveнаdocument- Правильное позиционирование
- Итого
Drag’n’Drop — это возможность захватить мышью элемент и перенести его. В свое время это было замечательным открытием в области интерфейсов, которое позволило упростить большое количество операций.
Одно из самых очевидных применений Drag’n’Drop - переупорядочение данных. Это могут быть блоки, элементы списка, и вообще - любые DOM-элементы и их наборы.
Перенос мышкой может заменить целую последовательность кликов. И, самое главное, он упрощает внешний вид интерфейса: функции, реализуемые через Drag’n’Drop, в ином случае потребовали бы дополнительных полей, виджетов и т.п.
HTML5 Drag’n’Drop
В современном стандарте HTML5 есть поддержка Drag’n’Drop при помощи специальных событий. Однако, эти события не поддерживаются Opera, в IE некоторая поддержка есть, но с ограничениями.
Их важная особенность заключается в том, что в Firefox и Safari/Chrome они позволяют обработать перенос файла в браузер, и при этом дают JavaScript доступ к содержимому этих файлов. Например, человек перенес картинку в редактор, JavaScript может закачать её на сервер и тут же отобразить. Для этой цели HTML5 события — единственное возможное решение.
События HTML5 заслуживают отдельного обсуждения как способ реализовать простые вещи просто (для тех браузеров, в которых это работает). Поэтому их использованию будет посвящена отдельная статья.
С другой стороны, у них есть и недостатки. Ряд сценариев сложно или нельзя реализовать при помощи встроенных событий (например, вставку между элементами).
Реализация переноса при помощи событий мыши надежна, кросс-браузерна, и позволяет делать всё, что захотим.
Далее речь пойдет о реализации Drag’n’Drop при помощи событий мыши.
Основная логика Drag’n’Drop
Для организации Drag’n’Drop нужно:
- При помощи события
mousedownотследить нажатие на переносимом элементе - При нажатии — подготовить мячик к перемещению: дать ему
position:absolute. При этом показать его на том же месте, назначивleft/topпо координатам курсора. Далее отслеживать движение мыши черезmousemoveи передвигать переносимый элемент по странице. Мяч уже имеетposition:absolute, так что это делается путем сменыleft/top. - При отпускании кнопки мыши, то есть наступлении события
mouseup— остановить перенос элемента и произвести все действия, связанные с окончанием Drag’n’Drop.
В следующем примере эти шаги реализованы. Мяч можно двигать мышью:
var ball = document.getElementById('ball');
ball.onmousedown = function(e) { // *!*отследить нажатие*/!*
var self = this;
e = fixEvent(e);
// подготовить к перемещению
// *!*разместить на том же месте, но в абсолютных координатах*/!*
this.style.position = 'absolute';
moveAt(e);
// переместим в body, чтобы мяч был точно не внутри position:relative
document.body.appendChild(this);
this.style.zIndex = 1000; // показывать мяч над другими элементами
// передвинуть мяч под координаты курсора
function moveAt(e) {
self.style.left = e.pageX-25+'px'; // 25 - половина ширины/высоты мяча
self.style.top = e.pageY-25+'px';
}
// *!*перемещать по экрану*/!*
document.onmousemove = function(e) {
e = fixEvent(e);
moveAt(e);
}
// *!*отследить окончание переноса */!*
this.onmouseup = function() {
document.onmousemove = self.onmouseup = null;
}
}
В действии:
Попробуйте этот пример в Firefox, в IE. Он не совсем работает, верно?
Это потому, что мы не учли ряд тонкостей, которые сейчас рассмотрим.
Отмена переноса браузера
При нажатии мышью браузер начинает выполнять свой собственный, встроенный Drag’n’Drop для изображений, который и портит наш перенос.
Чтобы браузер не вмешивался, нужно отменить действие для события dragstart:
ball.ondragstart = function() {
return false;
}
Исправленный пример:
var ball = document.getElementById('ball2');
ball.onmousedown = function(e) {
var self = this;
e = fixEvent(e);
this.style.position = 'absolute';
moveAt(e);
document.body.appendChild(this);
this.style.zIndex = 1000;
function moveAt(e) {
self.style.left = e.pageX-25+'px';
self.style.top = e.pageY-25+'px';
}
document.onmousemove = function(e) {
e = fixEvent(e);
moveAt(e);
}
this.onmouseup = function() {
document.onmousemove = self.onmouseup = null;
}
}
*!*
ball.ondragstart = function() {
return false;
};
*/!*
В действии:
mousemove на document
Почему событие mousemove в примере отслеживается на document, а не на ball?
..И правда, почему? С первого взгляда кажется, что мышь всегда над мячом и событие можно повесить на сам мяч, а не на документ.
Однако, на самом деле мышь во время переноса не всегда над мячом. Вспомните, браузер регистрирует mousemove часто, но не для каждого пикселя.
Быстрое движение курсора вызовет mousemove уже не над мячом, а, например, в дальнем конце страницы. Вот почему мы должны отслеживать mousemove на всём document.
Правильное позиционирование
В примерах выше мяч позиционируется в центре под курсором мыши:
self.style.left = e.pageX - 25 + 'px'; self.style.top = e.pageY - 25 + 'px';
Число 25 здесь — половина длины мячика. Оно использовано здесь потому, что если поставить left/top ровно в pageX/pageY, то мячик прилипнет верхним-левым углом к курсору мыши. Будет некрасиво.
Для правильного переноса необходимо, чтобы изначальный сдвиг курсора относительно элемента сохранялся.

- Когда человек нажимает на мячик
mousedown— курсор сдвинут относительно левого-верхнего угла мяча на расстояниеshiftX/shiftY.Получить значения
shiftX/shiftYлегко: достаточно вычесть из координат курсора левую-верхнюю границу мячика.
// onmousedown shiftX = e.pageX - getCoords(ball).left; shiftY = e.pageY - getCoords(ball).top;
- Далее при переносе мяча мы располагаем его
left/topс учетом сдвига, то есть вот так:
// onmousemove self.style.left = e.pageX - *!*shiftX*/!* + 'px'; self.style.top = e.pageY - *!*shiftY*/!* + 'px';
Пример с правильным позиционированием:
В этом примере позиционирование осуществляется не на 25px, а с учётом изначального сдвига.
var ball = document.getElementById('ball3');
ball.onmousedown = function(e) {
var self = this;
e = fixEvent(e);
var coords = getCoords(this);
*!*
var shiftX = e.pageX - coords.left;
var shiftY = e.pageY - coords.top;
*/!*
this.style.position = 'absolute';
document.body.appendChild(this);
moveAt(e);
this.style.zIndex = 1000; // над другими элементами
function moveAt(e) {
self.style.left = e.pageX - *!*shiftX*/!* + 'px';
self.style.top = e.pageY - *!*shiftY*/!*+ 'px';
}
document.onmousemove = function(e) {
e = fixEvent(e);
moveAt(e);
};
this.onmouseup = function() {
document.onmousemove = self.onmouseup = null;
};
}
ball.ondragstart = function() {
return false;
};
В действии:
Различие особенно заметно, если захватить мяч за правый-нижний угол. В предыдущем примере мячик «прыгнет» серединой под курсор, в этом — будет плавно переноситься с текущей позиции.
Итого
Мы рассмотрели «минимальный каркас» Drag'n'Drop.
Его компоненты:
- События
mousedown -> document.mousemove -> mouseup. - Передвижение с учётом изначального сдвига
shiftX/shiftY. - Отмена действия браузера по событию
dragstart.
На этой основе можно сделать очень многое.
- При
mouseup, можно обработать окончание переноса, произвести изменения в данных, если они нужны. - Во время самого переноса можно подсвечивать элементы, над которыми проходит элемент.
Это и многое другое мы рассмотрим в статье про Drag’n’Drop объектов.
Создайте слайдер:
Захватите мышкой синий бегунок и двигайте его, чтобы увидеть в работе.
Позже к этому слайдеру можно будет добавить дополнительные функции по чтению/установке значения.
Исходный документ: tutorial/browser/events/slider-simple-src/index.html (fixEvent/getCoords в lib.js).
Важно:
- Слайдер должен нормально работать при резком движении мыши влево или вправо, за пределы полосы. При этом бегунок должен останавливаться четко в нужном конце полосы.
- При нажатом бегунке мышь может выходить за пределы полосы.
Слайдер — это DIV, подкрашенный фоном/градиентом, внутри которого находится другой DIV, оформленный как бегунок, с position:relative.
Бегунок немного поднят, и вылезает по высоте из родителя.
Например, вот так:
<style>
.slider {
border-radius: 5px;
background: #E0E0E0;
background: -moz-linear-gradient(left top , #E0E0E0, #EEEEEE) repeat scroll 0 0 transparent;
background: -webkit-gradient(linear, left top, right bottom, from(#E0E0E0), to(#EEEEEE));
background: linear-gradient(left top, #E0E0E0, #EEEEEE);
width: 310px;
height: 15px;
margin: 5px;
}
.thumb {
width: 10px;
height: 25px;
border-radius: 3px;
position: relative;
left: 10px;
top: -5px;
background: blue;
cursor: pointer;
}
</style>
<div class="slider">
<div class="thumb"></div>
</div>
Теперь на этом реализуйте перенос бегунка.
Полное решение: tutorial/browser/events/slider-simple/index.html.
Это горизонтальный Drag’n’Drop, ограниченный по ширине. Его особенность — в position:relative у переносимого элемента, т.е. координата ставится не абсолютная, а относительно родителя.
Комментарии
- Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
- Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
- Комментарии без смысла, с рекламой или не о статье вообще - удаляются.