Мастер-классы по Javascript Екатеринбург Ростов-на-Дону Москва Узнать больше...
Содержание (скрыть) Содержание (показать)

Основы Drag'n'Drop

  1. HTML5 Drag’n’Drop
  2. Основная логика Drag’n’Drop
  3. Отмена переноса браузера
  4. mousemove на document
  5. Правильное позиционирование
  6. Итого

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 нужно:

  1. При помощи события mousedown отследить нажатие на переносимом элементе
  2. При нажатии — подготовить мячик к перемещению: дать ему position:absolute. При этом показать его на том же месте, назначив left/top по координатам курсора. Далее отслеживать движение мыши через mousemove и передвигать переносимый элемент по странице. Мяч уже имеет position:absolute, так что это делается путем смены left/top.
  3. При отпускании кнопки мыши, то есть наступлении события 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.

Его компоненты:

  1. События mousedown -> document.mousemove -> mouseup.
  2. Передвижение с учётом изначального сдвига shiftX/shiftY.
  3. Отмена действия браузера по событию dragstart.

На этой основе можно сделать очень многое.

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

Это и многое другое мы рассмотрим в статье про Drag’n’Drop объектов.

Создайте слайдер:

Захватите мышкой синий бегунок и двигайте его, чтобы увидеть в работе.

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

Исходный документ: tutorial/browser/events/slider-simple-src/index.html (fixEvent/getCoords в lib.js).

Важно:

  • Слайдер должен нормально работать при резком движении мыши влево или вправо, за пределы полосы. При этом бегунок должен останавливаться четко в нужном конце полосы.
  • При нажатом бегунке мышь может выходить за пределы полосы.
HTML/CSS, подсказка
Решение
HTML/CSS, подсказка

Слайдер — это DIV, подкрашенный фоном/градиентом, внутри которого находится другой DIV, оформленный как бегунок, с position:relative.

Бегунок немного поднят, и вылезает по высоте из родителя.

HTML/CSS, решение
HTML/CSS, решение

Например, вот так:

<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 у переносимого элемента, т.е. координата ставится не абсолютная, а относительно родителя.


Комментарии

  1. Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  2. Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
  3. Комментарии без смысла, с рекламой или не о статье вообще - удаляются.
Наверх

Содержание

Реклама

Нашли опечатку?

Нашли опечатку на сайте? Что-то кажется странным?
Выделите соответствующий текст и нажмите Ctrl+Enter!

Последние Комментарии

Помоги другим!

Помоги другим узнать о хорошей статье!