JavaScript Drag and Drop (Draggable, Movable) Element without External Library


There are many libraries like jQuery which provide plugins to make an DOM element movable and draggable. I would like, however, to know how to make an element draggable without external library. In [1], the author provides an implementation of draggable element both using keyboard and mouse. He also gave an post (see [3]) about HTML5 draggable issue. His implementation:

  1. includes both keyboard and mouse, but I want mouse only.
  2. this keyword in the callback function refers to the DOM element at which events occur, but I want this keyword to refer to the object which makes DOM element draggable.

In [2], the author also gives another implementation, but I like the code structure to be more "object-oriented" like the implementation in [1]. So I wrote an implementation of my own. The following is my implementation:

Demo

movable.html | repository | view raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Draggable (Movable, Drag and Drop) DOM Element Example</title>
</head>
<body>

<div id="dragMe" style="width: 200px; height: 200px; background: yellow;">Drag Me</div>

<script type="text/javascript" src="draggable.js"></script>
<script type="text/javascript">
  window.onload = function() {
    var drag = new Draggable('dragMe');
  }
</script>
</body>
</html>
draggable.js | repository | view raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
/**
 * @fileoverview Class to make DOM element draggable.
 *
 * References:
 * @see http://www.quirksmode.org/js/dragdrop.html
 * @see http://luke.breuer.com/tutorial/javascript-drag-and-drop-tutorial.aspx
 * @see http://help.dottoro.com/ljwcseaq.php
 * @see http://help.dottoro.com/ljlrboji.php
 * @see http://help.dottoro.com/ljsjcrav.php
 */


/**
 * Cross-browser addEventListener function.
 *
 * @param {DOM element} element The element to add event listener.
 * @param {string} evt The event to be listened.
 * @param {function} fn The callback function when event occurs.
 */
addEventListener = function(element, evt, fn) {
  if (window.addEventListener) {
    /* W3C compliant browser */
    element.addEventListener(evt, fn, false);
  } else {
    /* IE */
    element.attachEvent('on' + evt, fn);
  }
};


/**
 * Cross-browser removeEventListener function.
 *
 * @param {DOM element} element The element to remove event listener.
 * @param {string} evt The event to be un-listened.
 * @param {function} fn The callback function when event occurs.
 */
removeEventListener = function(element, evt, fn) {
  if (window.removeEventListener) {
    /* W3C compliant browser */
    element.removeEventListener(evt, fn, false);
  } else {
    /* IE */
    element.detachEvent('on' + evt, fn);
  }
};


/**
 * Class to make DOM element draggable.
 *
 * @param {string} id The id of DOM element to be draggable.
 * @constructor
 */
Draggable = function(id) {
  /**
   * The DOM element to be made draggable.
   * @type {DOM Element}
   * @private
   */
  this.draggedElement_ = document.getElementById(id);

  // the element to be made draggable must have CSS property
  // 'position: absolute;' or 'position: fixed;'
  this.draggedElement_.style.position = "absolute";

  if (!this.draggedElement_) {
    throw "Draggable.NoElement";
  }

  /**
   * Passing this.startMouseDraggable.bind(this) directly to addEventListener
   * and removeEventListener is WRONG because this is actually passing an
   * anonymous function as argument, which has no effect on removeEventListener.
   * The workaround is as below. Use an event handler object to wrap functions.
   * @see http://stackoverflow.com/questions/4386300/javascript-dom-how-to-remove-all-events-of-a-dom-object
   * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
   * @enum {function}
   */
  this.eventHandlers_ = {
    'startMouseDraggable': this.startMouseDraggable.bind(this),
    'mouseDrag'          : this.mouseDrag.bind(this),
    'releaseElement'     : this.releaseElement.bind(this)
  }

  /**
   * The initial X position of draggable DOM element.
   * @type {number}
   * @private
   */
  this.startX_ = undefined;

  /**
   * The initial Y position of draggable DOM element.
   * @type {number}
   * @private
   */
  this.startY_ = undefined;

  /**
   * The initial X position of mouse cursor of mouse down event.
   * @type {number}
   * @private
   */
  this.initialMouseX_ = undefined;

  /**
   * The initial Y position of mouse cursor of mouse down event.
   * @type {number}
   * @private
   */
  this.initialMouseY_ = undefined;

  // start to listen to mouse down event of draggable element
  addEventListener(this.draggedElement_, 'mousedown',
                        this.eventHandlers_.startMouseDraggable);
};


/**
 * start DOM element draggable mouse event.
 * @param {Object} e The event object passed by browser automatically in W3C-
 *                   compliant browser. For IE, use 'e || window.event'.
 * @private
 */
Draggable.prototype.startMouseDraggable = function(e) {
  var evt = e || window.event; // For IE compatible

  // suppress the default action of the mouse event: start selecting text.
  // maybe no need to 'return false;' at the end of this function.
  // @see http://stackoverflow.com/questions/1000597/event-preventdefault-function-not-working-in-ie
  if (evt.preventDefault) evt.preventDefault();
  if (evt.stopPropagation) evt.stopPropagation();
  if (window.event) evt.returnValue = false; // IE version of preventDefault

  // In case mouse move and up event have been previously registered
  this.releaseElement();

  // Set current position of dragged element
  this.startX_ = this.draggedElement_.offsetLeft;
  this.startY_ = this.draggedElement_.offsetTop;

  // Set current position of mouse cursor
  this.initialMouseX_ = evt.clientX;
  this.initialMouseY_ = evt.clientY;

  /**
   * From 'Drag and drop - QuirksMode':
   * However, the mousemove and mouseup event should be set not on the element,
   * but on the entire document. The reason is that the user may move the mouse
   * wildly and quickly, and he might leave the dragged element behind. If the
   * mousemove and mouseup functions were defined on the dragged element, the
   * user would now lose control because the mouse is not over the element any
   * more. That's bad usability.
   */
  addEventListener(document, 'mousemove',
                        this.eventHandlers_.mouseDrag);
  addEventListener(document, 'mouseup',
                        this.eventHandlers_.releaseElement);

  /**
   * From 'Drag and drop - QuirksMode':
   * suppress the default action of the mouse event: start selecting text.
   *
   * return false = evt.preventDefault + evt.stopPropagation
   * @see http://stackoverflow.com/questions/128923/whats-the-effect-of-adding-return-false-to-an-onclick-event
   * @see http://stackoverflow.com/questions/1357118/event-preventdefault-vs-return-false
   */
  return false;
};


/**
 * drag DOM element by mouse (mouse move event callback)
 * @param {Object} e The event object passed by browser automatically in W3C-
 *                   compliant browser. For IE, use 'e || window.event'.
 * @private
 */
Draggable.prototype.mouseDrag = function(e) {
  var evt = e || window.event; // For IE compatible

  // suppress the default action of mouse event
  // maybe no need to 'return false;' at the end of this function.
  // @see http://stackoverflow.com/questions/1000597/event-preventdefault-function-not-working-in-ie
  if (evt.preventDefault) evt.preventDefault();
  if (evt.stopPropagation) evt.stopPropagation();
  if (window.event) evt.returnValue = false; // IE version of preventDefault

  // calculate the delta of mouse cursor movement
  var dX = evt.clientX - this.initialMouseX_;
  var dY = evt.clientY - this.initialMouseY_;

  this.setPosition(dX,dY);

  // suppress the default action of mouse event
  return false;
};


/**
 * Set new position of dragged element by mouse dragging.
 * @param {number} dX The delta-X of mouse cursor movement
 * @param {number} dY The delta-Y of mouse cursor movement
 * @private
 */
Draggable.prototype.setPosition = function(dx, dy) {
  this.draggedElement_.style.left = this.startX_ + dx + 'px';
  this.draggedElement_.style.top  = this.startY_ + dy + 'px';
};


/**
 * stop listening to mouse Move and Up event.
 * @param {Object} e The event object passed by browser automatically in W3C-
 *                   compliant browser. For IE, use 'e || window.event'.
 * @private
 */
Draggable.prototype.releaseElement = function(e) {
  removeEventListener(document, 'mousemove',
                           this.eventHandlers_.mouseDrag);
  removeEventListener(document, 'mouseup',
                           this.eventHandlers_.releaseElement);
};

Note that the element to be made draggable must have CSS property position: absolute; or position: fixed; (position set to absolute in the JavaScript code). I put a lot of comments in the code to make the code understandable. Hope this would be helpful for those who are interested.

If you need draggable elements in AngularJS way, see [4].
If you need draggable elements in Dart, see [5].

References:

[1](1, 2) Drag and drop - QuirksMode
[2]JavaScript Drag and Drop Tutorial
[3]The HTML5 drag and drop disaster
[4][AngularJS] Draggable (Movable) Element
[5][Dart] Draggable (Movable) Element
[6][Golang] Draggable (Movable) Element by GopherJS