Creating a Draggable Element Using JavaScript

By
coding
javascript
css
ui/ux
Open in Demo.js

Elements with the possibility of dragging are a common feature in many web applications. It gives a better user experience, rich graphical representation, and rapid control within the user interface. In this article, we are going to show how we can create that kind of draggable element using JavaScript.

DRAG ME

First, we should create an element inside another, so the parent element will be the draggable space, and the child element will be the draggable element. It is better to put class names according to identifying their functionality.

<!-- container element -->
<div class="container">
  <!-- draggable element -->
  <div class="draggable">
    DRAG ME
  </div>
</div>

Then we can define some CSS rules. The most important Thing to do here is to set the parent element position as relative and the child element position as absolute. From that configuration, we can place the child element inside the parent element using left and top rules. And make sure to avoid drag and scroll action in mobile devices using the touch-action rule on the container element.

/* container style */
.container {
  /* dimensions */
  width: 200px;
  height: 200px;
  /* visibility */
  border: 2px solid #888;
  background-color: #666;
  /* child positioning */
  position: relative;
  /* avoid drag scroll on mobile */
  touch-action: none;
  /* avoid content selection */
  user-select: none;
}

.draggable {
  /* dimensions */
  width: 50px;
  height: 50px;
  /* visibility */
  border: 2px solid #888;
  background-color: #444;
  /* positioning */
  position: absolute;
  left: 10px;
  top: 10px;
}

Now, within JavaScript, we can define the two elements. And some states that are going to be useful while element dragging. Since JavaScript has no native way of detecting element dragging, we have to combine pointer down and pointer move events to identify the drag action. That's why we have an active boolean state. Also, we have to remember the event when dragging starts and also the element position at the same time. This information will be useful to calculate the dragging offset in the future.

// get container and draggable elements
const container = document.querySelector(".container")
const draggable = document.querySelector(".draggable")

// draggable states
const states = {
  // dragging active status
  active: false,
  // pointer event on drag start
  originEvent: null,
  // element position on drag start
  originPoint: { x: 10, y: 10 }
}

We should have a helper function to extract pointer events from desktop and mobile devices. Since we receive pointer coordinates in two different ways from those devices, it is much easier to extract them from a helper function.

// helper to get event coordinates
const getEventCoords = event => ({
  // get x position from desktop or mobile event
  x: event.clientX ?? event.touches[0].clientX,
  // get y position from desktop or mobile event
  y: event.clientY ?? event.touches[0].clientY
})

Next, we have the drag start event handling. This is where the user points down on the draggable element before starting to drag. At this moment we have to set the active state to true and store that event, which contains to pointer coordinates. And also extract the current fixed position of the draggable element. We should add event listeners for both desktop and mobile devices, binding this drag start event handler.

// drag start event
const dragStart = event => {
  // set as dragging active
  states.active = true
  // store origin event
  states.originEvent = event
  // get computed style from element
  const style = getComputedStyle(draggable)
  // store origin point
  states.originPoint.x = parseFloat(style.left)
  states.originPoint.y = parseFloat(style.top)
}

// pointer down events on draggable element
draggable.addEventListener("mousedown", dragStart)
draggable.addEventListener("touchstart", dragStart)

Then we can handle the dragging event. Here we avoid the handler event if user hasn't press the pointer down which the active state. If dragging is active, we can extract current pointer coordinates, also the origin pointer coordinates and subtract them to find dragged distance in both x and y direction. Then we add the origin element left and top position to each direction so we can get the real-time dragging position and update on the element. If you have a limited draggable space you can use Math.min and Math.max methods to clamp the values into the valid range.

// drag move event
const dragMove = event => {
  // return if not dragging
  if (!states.active) { return }
  // get coordinates of events
  const pointA = getEventCoords(states.originEvent)
  const pointB = getEventCoords(event)
  // get dragging offsets
  const dx = pointB.x - pointA.x
  const dy = pointB.y - pointA.y
  // calculate and clamp current position
  const x = Math.min(Math.max(states.originPoint.x + dx, 5), 142)
  const y = Math.min(Math.max(states.originPoint.y + dy, 5), 142)
  // update draggable element position
  draggable.style.left = `${x}px`
  draggable.style.top = `${y}px`
}

// pointer move events on container element
container.addEventListener("pointermove", dragMove)
container.addEventListener("touchmove", dragMove)

Finally, we should reset the active state when the user releases the pointer or drags the pointer out of the draggable space.

// drag stop event
const dragStop = () => {
  // set as dragging inactive
  states.active = false
}

// pointer stop events on container element
container.addEventListener("mouseup", dragStop)
container.addEventListener("mouseleave", dragStop)