Драг на JavaScript

;

О чем это я?
Иногда разработчик сталкивается с необходимостью разрешить пользователю свободно перемещать некоторые объекты на странице. Например, такая ситуация возникает при программировании некоторых JavaScript-игр. В этой статье изложена общая теория программирования драга на JavaScript.

Словарь чисто технических терминов ;)
хомяк - мышь
белый кролик - курсор хомяка
драг (тяг) объекта - перетаскивание объекта белым кроликом

Принципы управления драгом.
Сам по себе, объект, над которым совершается попытка драга, не будет перемещаться. Но мы заставим его идти за белым кроликом :) И не таких заставляли ;)

Сделать это можно, задав обработчики "тягучих" (dragging) событий: ondragstart, ondrag, ondragend - для перемещаемого объекта. Но. Мы пойдем другим путем. Поясню почему. При драге объекта <img> на событии ondrag белый кролик приобретает форму перечеркнутой окружности, которая (форма) всем видом своим намекает на то, что объект перетягивать нельзя. И избавиться от этого побочного эффекта мне не удалось, как я ни хитрил ;) Кроме того, drag-события поддерживаются не всеми версиями современных браузеров (например, Opera6 - не поддерживает). Поэтому будем применять менее специализированные обработчики событий onmousedown, onmousemove и onmouseup. Отмечу, что при использовании этих событий можно установить произвольную форму белого кролика.

Разберем названные события:

onmousedown - возникает при нажатии на любую кнопку хомяка. Внутри обработчика event.button указывает, какая кнопка нажата: 1 = левая, 2 = правая.

onmousemove - возникает при перемещении белого кролика по экрану. Внутри обработчика event.x и event.y - текущие координаты "горячей" точки белого кролика на экране.

onmouseup - возникает при отпускании любой из нажатых кнопок хомяка. Определение, какая кнопка отпущена - как и для onmousedown выше.

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

Обратимся к техническим деталям реализации драга для объекта img.
Во-первых, нужно установить стилевой параметр position в значение absolute (чтобы не привязываться ни к каким относительным координатам охватывающего объекта) и задать начальные координаты. Во-вторых, нужно установить указанные обработчики событий. Так что, определение перетаскиваемой картинки может быть таким (для некоторого файла star.gif):

<img   src="star.gif"   style="position: absolute; left: 100px; top: 100px;"   onmousedown="mousedown(this);"   onmousemove="mousemove(this); return false;"   onmouseup="mouseup(this);"   oncontextmenu="return false;"  >

Как видно, обработчик oncontextmenu просто возвращает значение false, что приводит к "обрыву" цепочки обработчиков этого события. Т.е. мы указываем, что событие целиком и полностью отработано нами и дальнейшей отработке не подлежит. То же верно и для mousemove (при отладке выяснилось, что нужно поставить "обрыв").

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

<script language="JavaScript">  /*    Сервисная функция.    Возвращает значение свойства pr    объекта obj в формате целого числа.  */  function getPr( obj, pr) {   return parseInt(obj[pr],10);  }    /*    Переменные для фиксирования координат    "горячей" точки белого кролика    и состояния левой кнопки хомячка.  */  var x, y, mouse_state = 0;    function mousedown( obj) {   if (event.button==1) { /* проверяем нажатую кнопку */    mouse_state = 1; /* левая кнопка нажата */  /*    сохраняем текущие координаты    "горячей" точки белого кролика  */    x = event.x;    y = event.y;    }  }    function mousemove( obj) {   if (mouse_state) { /* проверяем, нажата ли кнопка */    /* производим перемещение объекта */    obj.style.left = getPr(obj.style,'left')+(event.x-x);    obj.style.top = getPr(obj.style,'top')+(event.y-y);  /*    сохраняем текущие координаты    "горячей" точки белого кролика  */    x = event.x;    y = event.y;   }  }    function mouseup( obj) {   if ((event.button==1)&&(mouse_state))  /* проверяем отжатую кнопку  */    mouse_state = 0; /* левая кнопка не нажата */  }  </script>