Drag and Drop in Vanilla JavaScript

Introduction

The act of picking an item or chunk of text, moving it (dragging), and then placing it (dropping) in another location is described is known as drag-and-drop functionality.

Most browsers make text selections, pictures, and links draggable by default. For example, if you drag images or image-based logotypes on any website, a "ghost image" will appear (this doesn't work for SVG, since those aren't images).

Note: To make other types of content draggable you need to use the HTML5 Drag and Drop (DnD) APIs or an external JavaScript library.

In this hands on guide, we'll take a look at how to implement drag-and-drop in JavaScript by using both the Drag and Drop (DnD) API and an external JavaScript library, sortable.

The massively popular Kanban board, Trello, makes use of drag-and-drop to make movements of cards from one list to another easier! In this guide, we'll be building something very similar.

Using the HTML5 Drag and Drop API+

To implement drag and drop functionality in conventional HTML4, developers had to utilize difficult JavaScript programming or other JavaScript frameworks like jQuery, etc., but HTML 5 introduced a Drag and Drop (DnD) API that delivers native DnD support to the browser, making it much easier to code up!

All major browsers, including Chrome, Firefox 3.5, and Safari 4, support this HTML 5 DnD.

We can make practically any element on our website draggable using the API. With a mouse, the user may pick draggable items, drag them to a droppable element, then drop them by releasing the mouse button. This makes use of both the DOM event paradigm and the drag and drop events.

Note: Several event types are fired during drag operations, and certain events, such as the drag and dragover events, may fire several times.

Drag and Drop Events

A number of events are triggered at various phases of the drag and drop procedure:

  • dragstart: When the user begins dragging the item, this event occurs.
  • dragenter: When the mouse is moved over the target element for the first time when dragging, this event is triggered.
  • dragover: When a drag occurs, this event is triggered when the mouse is dragged over an element. The process that happens during a listener is frequently the same as the dragenter event.
  • dragleave: When the mouse leaves an element while dragging, this event is triggered.
  • drag: When the mouse is moved while the item is being dragged, this event is triggered.
  • drop: At the completion of the drag operation, the drop event is fired on the element where the drop happened. A listener would be in charge of obtaining the dragged data and putting it at the drop place.
  • dragend: When the user releases the mouse button while dragging an item, this event occurs.

Getting Started

Let's build a simple copy of a Trello board! The result will look something along these lines:

Creating the Project and Initial Markup

Let's create the basic structure in HTML - a container with several column elements acting as lists of tasks. Say, the first list, corresponding to the "All Tasks" column, has all of the tasks initially, which we'll be able to drag and drop to other columns:

<div class="container">
    <div class="column">
        <h1>All Tasks</h1>
        <div class="item">Wash Clothes</div>
        <div class="item">Meeting at 9AM</div>
        <div class="item">Fix workshop</div>
        <div class="item">Visit the zoo</div>
    </div>
    <div class="column">
        <h1>In progress</h1>
    </div>
    <div class="column">
        <h1>Paused</h1>
    </div>
    <div class="column">
        <h1>Under Review</h1>
    </div>
    <div class="column">
        <h1>Completed</h1>
    </div>
</div>

Let's add some rudimentary style to the container, columns and items:

.container{
    font-family: "Trebuchet MS", sans-serif;
    display: flex;
    gap: 30px;
}
.column{
    flex-basis: 20%;
    background: #ddd;
    min-height: 90vh;
    padding: 20px;
    border-radius: 10px;
}
.column h1{
    text-align: center;
    font-size: 22px;
}
.item{
    background: #fff;
    margin: 20px;
    padding: 20px;
    border-radius: 3px;
    cursor: pointer;
}
.invisible{
    display: none;
}

The page should look something along these lines:

Making an Object Draggable

These objects aren't draggable yet, though. They're just there! To make an object draggable - we set its draggable attribute to true. Anything on your website, including photos, files, links and files, can be dragged!

Let's set draggable="true" on our item elements:

<div class="column">
    <h1>All Tasks</h1>
    <div class="item" draggable="true">Wash Clothes</div>
    <div class="item" draggable="true">Meeting at 9AM</div>
    <div class="item" draggable="true">Fix workshop</div>
    <div class="item" draggable="true">Visit the zoo</div>
</div>

Now that the elements are draggable, they can emit dragging events! Let's set up event listeners to pick those up, and react to the events.

Handling Drag-and-Drop Events with JavaScript

Let's gather all of the items and columns on which we want to implement drag-and-drop. We can easily gather them using the document.querySelectorAll() DOM selectors! This will yield a NodeList array, which we can loop over to operate with each individual item/column:

const items = document.querySelectorAll('.item')
const columns = document.querySelectorAll('.column')

Naturally - if you don't have a list of items to work with, you can select them individually!

Let's loop through the elements, and add an event listener to each. We'll add an event listener for the dragstart and dragend events, and the functions to run when they fire:

items.forEach(item => {
    item.addEventListener('dragstart', dragStart)
    item.addEventListener('dragend', dragEnd)
});

dragStart() will run on each 'dragstart' event, and dragEnd() will run on each 'dragend' event.

Note: These functions can be used to add styling for better visual interactivity when users drag a particular item and drop them, such as a smooth animation of the card you're moving.

Let's test the functionality out by just logging messages:

function dragStart() {
    console.log('drag started');
}
function dragEnd() {
    console.log('drag ended');
}

Great! When an element is dragged around - the events are firing. Now, instead of just logging the message, let's apply a class name to the card instead. Let's start out by making the moved card invisible so it disappears from the original list. We'll style the dragged element and add logic for it to appear in a new list a bit later.

It is not necessary to make the element disappear; you can also make it fade by adjusting the opacity to illustrate that it is being dragged from one location to another. Feel free to get creative!

Let's modify the dragStart() function:

function dragStart() {
    console.log('drag started');
    setTimeout(() => this.className = 'invisible', 0)
}

Now - we don't just interact with the cards. We also want to interact with each column to accept a new card and to remove cards from old columns. For this, we'll want to run methods when events on the columns fire, just like for the items!

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

Let's loop through and add event listeners to the columns:

columns.forEach(column => {
    column.addEventListener('dragover', dragOver);
    column.addEventListener('dragenter', dragEnter);
    column.addEventListener('dragleave', dragLeave);
    column.addEventListener('drop', dragDrop);
});

Let's test the event listeners out:

function dragOver() {
    console.log('drag over');
}
function dragEnter() {
    console.log('drag entered');
}
function dragLeave() {
    console.log('drag left');
}
function dragDrop() {
    console.log('drag dropped');
}

When you view this in your browser, when you drag items, they should disappear and console logs should pop up when you "cross into" a new column with the item:

Note: If you carefully examine the console area, you will discover that the dragDrop() method did not log a message. To make this work, you must disable the default behavior in the dragOver() method.

function dragOver(e) {
  e.preventDefault()
  console.log('drag over');
}

You'll be able to notice that "drag dropped" is logged now, when you drop the item.

These are all the events we need! Now, we just want to implement the logic of removing items, adding them to new columns when they're dropped, etc. Since there's only one item we'll be dragging at one point in time, let's create a global variable for it. Since we'll change the reference commonly, it'll be a let, not a const.

When the drag is being initiated - we'll set this element to the dragItem, adding it to the column we're dropping to, and set it to null:

let dragItem = null;

function dragStart() {
    console.log('drag started');
    dragItem = this;
    setTimeout(() => this.className = 'invisible', 0)
}

function dragEnd() {
    console.log('drag ended');
      this.className = 'item'
      dragItem = null;
}

function dragDrop() {
    console.log('drag dropped');
    this.append(dragItem);
}

Note: Each element that emits events can be accessed through the this keyword, within the method called when the event is fired.

That's it - we can drag and drop cards from one column to another now:

We don't need to use all of the available events to make this work - they're added and can be used to further stylize the process.

One thing to note is - we sequentially add elements to the end of each column, always, since we don't keep track of their relative position and just call append() when required. This is easily fixable with the Sortable library!

Implementing Drag and Drop Using SortableJS

Sortable is a lightweight and simple JavaScript module that uses the native HTML5 drag and drop API to sort a list of objects, just like we have! It is compatible with all contemporary browsers and touch devices.

It's great for sorting elements within a list and allows you to drag and drop elements within a column, in different positions, rather than just between columns. This'll be a great addition for our application!

As a matter of fact - using Sortable, we can fully automate the whole process by having a group of columns, where each can share items.

Sortable can be imported via a CDN:

<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.14.0/Sortable.min.js" integrity="sha512-zYXldzJsDrNKV+odAwFYiDXV2Cy37cwizT+NkuiPGsa9X1dOz04eHvUWVuxaJ299GvcJT31ug2zO4itXBjFx4w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Or installed via NPM:

$ npm install sortablejs --save

Using Sortable is as easy as instantiating a Sortable object, on a given HTML element:

const column = document.querySelector('.column');

new Sortable(column, {
    animation: 150,
    ghostClass: 'blue-background-class'
});

There's a decent amount of properties you can set to customize the process - two of which we've used. The animation is animation time, expressed in milliseconds, while the ghostClass can be used to stylize how the "ghost" of the dragged element looks like! This makes for a much nicer experience of dragging an element.

Let's revert back to our Trello example, and apply Sortable to the task! It requires us to use the list-group-item class instead of item:

<div class="container">
    <div class="column">
        <h1>All Tasks</h1>
        <div class="list-group-item" draggable="true">Wash Clothes</div>
        <div class="list-group-item" draggable="true">Take a stroll outside</div>
        <div class="list-group-item" draggable="true">Design Thumbnail</div>
        <div class="list-group-item" draggable="true">Attend Meeting</div>
        <div class="list-group-item" draggable="true">Fix workshop</div>
        <div class="list-group-item" draggable="true">Visit the zoo</div>
    </div>
    <div class="column">
        <h1>In progress</h1>
    </div>
    <div class="column">
        <h1>Paused</h1>
    </div>
    <div class="column">
        <h1>Under Review</h1>
    </div>
    <div class="column">
        <h1>Completed</h1>
    </div>
</div>

Let's apply the same style as before:

.container {
    font-family: "Trebuchet MS", sans-serif;
    display: flex;
    gap: 30px;
}
.column {
    flex-basis: 20%;
    background: #ddd;
    min-height: 90vh;
    padding: 5px;
    border-radius: 10px;
}
.column h1 {
    text-align: center;
    font-size: 22px;
}
.list-group-item {
    background: #fff;
    margin: 20px;
    padding: 20px;
    border-radius: 5px;
    cursor: pointer;
}

Now, let's instantiate a Sortable for each column on the page, setting their group to "shared" so the cards are shareable between columns:

const columns = document.querySelectorAll(".column");

columns.forEach((column) => {
    new Sortable(column, {
        group: "shared",
        animation: 150,
        ghostClass: "blue-background-class"
    });
});

That's it! Sortable takes care of the rest:

Conclusion

In this article, we have taken a look at how to drag and drop elements in HTML5 and also using the JavaScript sortable library.

Last Updated: October 30th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Joel OlawanleAuthor

Frontend Developer & Technical Writer

Project

React State Management with Redux and Redux-Toolkit

# javascript# React

Coordinating state and keeping components in sync can be tricky. If components rely on the same data but do not communicate with each other when...

David Landup
Uchechukwu Azubuko
Details

Getting Started with AWS in Node.js

Build the foundation you'll need to provision, deploy, and run Node.js applications in the AWS cloud. Learn Lambda, EC2, S3, SQS, and more!

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms