Simple tutorial for Drag and Drop in HTML5

Drag and Drop is a the basic feature needed when talking about the application handling Media or shuffling Data. This post is a simple tutorial for Drag and Drop in HTML5 with JavaScript.

Simple tutorial for Drag and Drop in HTML5

Drag and Drop is an essential feature when talking about the application handling Media or shuffling Data.

Today we will look at the simple tutorial for Drag and Drop in HTML5.


If you are dealing with Media, you will need a Drop area (Dropzone) in the application and the case of Data Shuffling, both Draggable elements and Dropzone.

An element can be enabled to be dragged with dragable attribute to the element as follows:

<div draggable="true" class="card">
  ...
</div>

With this attribute being present with the element, you can register the event handlers for the following events:
HTML Drag and Drop API - Web APIs | MDN

  • drag
  • dragstart
  • dragend
  • dragenter
  • dragover
  • dragleave
  • dragexit
  • drop

As the element mentioned above is draggable, you can listen for dragger and drop events on the target element. As follows, in addition to the above source:

<div class="draggable-items">
  <div class="row">
    <div class="col">
      <div class="card bg-light my-3" draggable="true">
        <div class="card-header">Light card title</div>
        <div class="card-body">
          <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
        </div>
      </div>
      ...
    </div>
  </div>
</div>
<div class="row dropzones">
  <div class="col">
    <div class="my-3 p-3 border rounded dropzone">
      <span class="text-muted">Drop the cards here</span>
    </div>
  </div>
  ...
</div>

And the JS to make it work will go as follows:

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

let el = null;

document
  .querySelector('.draggable-items')
  .addEventListener('dragstart', e => {
    el = e.target.cloneNode(true)
    el.removeAttribute('draggable');
  })

dropzones.addEventListener('dragover', (e) => {
  e.preventDefault();
})
dropzones.addEventListener('dragenter', (e) => {
  if (e.target.classList.contains('dropzone')) {
    e.target.classList.add('solid-border');
  }
})
dropzones.addEventListener('drop', (e) => {
  e.preventDefault();
  e.target.appendChild(el);
  el = null;
  e.target.classList.remove('solid-border');
})

dropzones.addEventListener('dragleave', (e) => {
  if (e.target.classList.contains('dropzone')) {
    e.target.classList.remove('solid-border');
  }
})

The key thing to note here is that to allow drag event to happen on the dropzone element, you need to capture dragover event and prevent the events’ default action by event.preventDefault()

Then in the drop event, you can access the data transfer attribute from the event and take necessary action.

And For files, you need to capture dragenter, dragover, dragleave and drop events on the dropzone element.

In the case of files, the drop event has files property in the dataTransfer property of event, and then you can access the files in the similar way you would handle input type file

The following JS code will handle the file drops in a dropzone:

const events = [
  'dragenter',
  'dragleave',
  'dragover', // to allow drop
  'drop'
];
events.forEach(e => {
  fileDropZone.addEventListener(e, (ev) => {
    ev.preventDefault();
    if (ev.type === 'dragenter') {
      fileDropZone.classList.add('solid-border');
    }
    if (ev.type === 'dragleave') {
      fileDropZone.classList.remove('solid-border');
    }
    if(ev.type === 'drop') {
      fileDropZone.classList.remove('solid-border');
      handleFiles(ev.dataTransfer.files)
        .then(values => values.map(tag => {
          tag.setAttribute('class', 'border rounded img-preview');
          fileDropZone.appendChild(tag)
        }));
    }
  })
})


const handleFiles = (_files)  => {
  const files = Array.prototype.slice.call(_files);
  return Promise.all(files.map(imgForFile)) 
}

const generatePreviewData = (file) => {
  const fr = new FileReader();
  return new Promise((resolve, reject) => {
    fr.addEventListener('load', (e) => {
      resolve(fr.result);
    });
    fr.addEventListener('error', (e) => {
      reject();
    });
    fr.readAsDataURL(file);
  });
}

const imgForFile = (file) => {
  return generatePreviewData(file)
    .then((data) => {
      const img = document.createElement('img');
      img.src = data;
      img.height = 200;
      return img;
    })
}

Here in the demo or example, the asynchronous operation generates the preview of the images. You can also use it to upload files to the Backend.

For the image preview, we have used the code from the previous post:

Generate instant previews for image upload - Time to Hack
Quick and Easy solution to generate preview of images upload in vanilla javascript in callback and Promise style.

⚠️ Attention

Thin dataTransfer

While setting dataTransfer items, keep this info as less as possible.

Why? As the events are propagated to the document root and if other delegated handlers are attached, the event processing might slow down.

preventDefault

Another critical thing to notice is to preventDefault actions, as the browser might react to file drops or link drops and result in unwanted behaviours.


Demo Code

Let me know through comments ? or on Twitter at @heypankaj_ and/or @time2hack

If you find this article helpful, please share it with others ?

Subscribe to the blog to receive new posts right in your inbox.