Creating a Draggable Element Using JavaScript
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.
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)