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.

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

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

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

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

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

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

As the element we mentioned above is dragable; you can listen for dragger and drop events on the target element. Like as follows in addition to 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 is 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 dropzone element.

In 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

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, I have used the preview generation promise but you can also use it to upload files to Backend.

For this case, we have just used the code from post Generate instant previews for image upload to generate previews for the dropped images.

Good thing to notice here is that while setting dataTransfer item, keep this info as less as possible. Reason to do so is that the events are propagated to the document root and if other delegated handlers are attached, the event processing might slow down.

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


Demo Code

Let me know your thoughts and opinions about this article through comments ๐Ÿ’ฌ or on twitter at @patel_pankaj_ and @time2hack.

If you find this article useful, share it with others ๐Ÿ—ฃ