Zac Fukuda
059

Make HTML Element Draggable

Sample code to make an HTML element draggable.

This is different from how to implement html drag and drop API. The goal of this sample code is to move one html element freely inside window.

Codebase

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Draggable</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="draggable">Draggable</div>
  <script src="app.js"></script>
</body>
</html>
style.css
@charset "utf-8";

.draggable {
  position:  absolute;
  border: 1px solid #ddd;
  border-radius: 2px;
  background: #f0f0f0;
  display: inline-block;
  padding: 8px;
  cursor: grab;
}
.draggable:active {
  cursor: grabbing;
}

/* Only for demo */
html, body {
  width: 100%;
  height: 100%;
}

body {
  margin: 0;
  font-family: sans-serif;
}
app.js
const draggable = document.querySelector('.draggable')
const unit = 'px'
let diffX, diffY

function startDrag(e) {
  e.preventDefault()
  
  const { x, y } = draggable.getBoundingClientRect()

  diffX = e.x - x
  diffY = e.y - y
  document.onmousemove = drag
}

function drag(e) {
  e.preventDefault()
  draggable.style.left = (e.x - diffX) + unit
  draggable.style.top = (e.y - diffY) + unit
}

function endDrag() {
  document.onmousemove = null
}

draggable.onmousedown = startDrag
draggable.onmouseup = endDrag

Why Not Drag & Drop API

Bit of notes why the sample code don’t use drag and drop api, more specifically the dragstart, drag, and dragend events.

If we rewrite the js code above with drag and drop api, the code would be like this:

const draggable = document.querySelector('.draggable')
const unit = 'px'
let diffX, diffY

function startDrag(e) {	
  const { x, y } = draggable.getBoundingClientRect()

  diffX = e.x - x
  diffY = e.y - y
  draggable.ondrag = drag
}

function drag(e) {
  draggable.style.left = (e.x - diffX) + unit
  draggable.style.top = (e.y - diffY) + unit
}

function endDrag() {
  draggable.ondrag = null
}

draggable.ondragstart = startDrag
draggable.ondragend = endDrag

This code has the following issues:

One: it displays the dragged element and its projection.

Projection
Overlap of element & projection

This is not much recognized with the implementation above, but if we don’t change the position of dragged element as we move a mouse, the dragged element and projection would look like:

Drag & Drop Projection
Pure projection of drag & drop

Not only is this projection unnecessary as we just want to move the element but also can be displayed outside browser window.

projection outside browser
Projection outside browser

Furthermore, if we take a closer look, there are a few milliseconds of misalignment between the element and its projection as we move a mouse.

Two: the last DragEvent of drag() call has properties of x:0 and y:0, which positions the dragged element to the left top of window overflown.

projection overflown
element on top left

Nevertheless, we do have more control on the end implementation with MouseEvent than with DragEvent because the latter is more abstractive and designed for the specific purpose.