Simple Drag-and-Drop File Upload With JS HTML5 (Free Download)

Welcome to a quick tutorial on how to create a simple drag-and-drop file upload with Javascript and HTML. Forget those old boring “select a file” uploads. The Stone Age of the Internet is long over and it is time to create more exciting uploads. A drag-and-drop upload is actually not really that difficult to implement, and we can do it with just pure simple vanilla Javascript. Read on to find out how!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

TABLE OF CONTENTS

Download & Demo How It Works Useful Bits & Links
The End

 

 

DOWNLOAD & DEMO

Firstly, here is the download link to the example code as promised.

 

EXAMPLE CODE DOWNLOAD

Click here to download the source code in a zip file – I have released it under the MIT License, so feel free to build on top of it if you want to.

 

QUICK NOTES

If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

DRAG-DROP UPLOAD DEMO

dd-upload.html
<!-- (A) CSS + JS -->
<link rel="stylesheet" href="dd-upload.css"/>
<script src="dd-upload.js"></script>
 
<!-- (B) FILE DROP ZONE -->
<div id="demo"></div>
 
<!-- (C) ATTACH -->
<script>
ddup.init({
  target : document.getElementById("demo"), // target html <div>
  action : "dd-upload.php", // server-side upload handler
  data : { key : "value" } // optional, extra post data
});
</script>

For you guys who just want to use this as a “plugin”:

  1. Captain Obvious, load the CSS and Javascript.
  2. Define an empty <div> to generate the uploader.
  3. Use ddup.init() to attach the uploader.
    • target Required, HTML <div> to generate the uploader.
    • action Required, server-side script that will be handling the upload.
    • data Optional, additional data to POST to the server.

P.S. No actual upload will happen here, this demo will only show that drag-and-drop works.

 

 

HOW DRAG-AND-DROP UPLOAD WORKS

All right, let us now get into the details of how the drag-and-drop uploader works.

 

PART A) INITIALIZE UPLOADER

dd-upload.js
// (A) ATTACH DRAG-DROP FILE UPLOADER
init : (instance) => {
  // (A1) FLAGS + CSS CLASS
  instance.target.classList.add("upwrap");
  instance.upqueue = []; // upload queue
  instance.uplock = false; // uploading in progress
 
  // (A2) DRAG-DROP HTML INTERFACE
  instance.target.innerHTML =
    `<div class="updrop">Drop Files Here To Upload.</div>
     <div class="upstat"></div>`;
  instance.hzone = instance.target.querySelector(".updrop");
  instance.hstat = instance.target.querySelector(".upstat");
 
  // (A3) HIGHLIGHT DROP ZONE ON DRAG ENTER
  instance.hzone.ondragenter = (e) => {
    e.preventDefault();
    e.stopPropagation();
    instance.hzone.classList.add("highlight");
  };
  instance.hzone.ondragleave = (e) => {
    e.preventDefault();
    e.stopPropagation();
    instance.hzone.classList.remove("highlight");
  };
 
  // (A4) DROP TO UPLOAD FILE
  instance.hzone.ondragover = (e) => {
    e.preventDefault();
    e.stopPropagation();
  };
  instance.hzone.ondrop = (e) => {
    e.preventDefault();
    e.stopPropagation();
    instance.hzone.classList.remove("highlight");
    ddup.queue(instance, e.dataTransfer.files);
  };
},

You already know this one, we call ddup.init() to attach the uploader to an HTML <div>. A quick walkthrough:

  • (A1) Take extra note of the instance.upqueue and instance.uplock here, these are necessary for the upload queue later.
  • (A2) We generate 2 more sections within the target: <div>.
    • <div class="updrop"> The “dropzone”, drop files here to upload.
    • <div class="upstat"> Upload status.
  • (A3 & A4) Drag-and-drop listeners. Drop a file to add to the queue, start the upload.

 

 

PART B) UPLOAD QUEUE

dd-upload.js
// (B) UPLOAD QUEUE
// * AJAX IS ASYNCHRONOUS, UPLOAD QUEUE PREVENTS SERVER FLOOD
queue : (instance, files) => {
  // (B1) PUSH FILES INTO QUEUE + GENERATE HTML ROW
  for (let f of files) {
    f.hstat = document.createElement("div");
    f.hstat.className = "uprow";
    f.hstat.innerHTML =
      `<div class="upfile">${f.name} (${f.size} bytes)</div>
       <div class="upprog">
         <div class="upbar"></div>
         <div class="uppercent">0%</div>
       </div>`;
    f.hbar = f.hstat.querySelector(".upbar");
    f.hpercent = f.hstat.querySelector(".uppercent");
    instance.hstat.appendChild(f.hstat);
    instance.upqueue.push(f);
  }
 
  // (B2) UPLOAD!
  ddup.go(instance);
},

When files are dropped into the “dropzone”, ddup.queue() will loop through all the files:

  • Create the corresponding HTML file upload status.
  • Push the file into instance.upqueue.

The reason for this “gimmick” is simple – AJAX is asynchronous. It will be bad if the user drops hundreds of files, and the server has to handle hundreds of parallel uploads at once. So instead of a parallel upload, we will loop through instance.upqueue and upload one-by-one instead.

 

 

PART C) AJAX UPLOAD

dd-upload.js
// (C) AJAX UPLOAD
go : (instance) => { if (!instance.uplock && instance.upqueue.length!=0) {
  // (C1) UPLOAD STATUS UPDATE
  instance.uplock = true;
 
  // (C2) PLUCK OUT FIRST FILE IN QUEUE
  let thisfile = instance.upqueue[0];
  instance.upqueue.shift();
 
  // (C3) UPLOAD DATA
  let data = new FormData();
  data.append("upfile", thisfile);
  if (instance.data) { for (let [k, v] of Object.entries(instance.data)) {
    data.append(k, v);
  }}
 
  // (C4) AJAX UPLOAD
  let xhr = new XMLHttpRequest();
  xhr.open("POST", instance.action);
 
  // (C5) UPLOAD PROGRESS
  let percent = 0, width = 0;
  xhr.upload.onloadstart = (evt) => {
    thisfile.hbar.style.width = 0;
    thisfile.hpercent.innerHTML = "0%";
  };
  xhr.upload.onloadend = (evt) => {
    thisfile.hbar.style.width = "100%";
    thisfile.hpercent.innerHTML = "100%";
  };
  xhr.upload.onprogress = (evt) => {
    percent = Math.ceil((evt.loaded / evt.total) * 100) + "%";
    thisfile.hbar.style.width = percent;
    thisfile.hpercent.innerHTML = percent;
  };
 
  // (C6) UPLOAD COMPLETE
  xhr.onload = function () {
    // (C6-1) ERROR
    if (this.response!= "OK" || this.status!=200) {
      thisfile.hpercent.innerHTML = this.response;
    }
    // (C6-2) NEXT BETTER PLAYER!
    else {
      thisfile.hbar.style.width = "100%";
      thisfile.hpercent.innerHTML = "100%";
      instance.uplock = false;
      ddup.go(instance);
    }
  };

  // (C7) GO!
  xhr.send(data);
}}

Lastly, the ddup.go() function is the one that does the actual upload. Nothing special here, just the good old AJAX. Also, remember using the uplock flag to restrict one at a time? Just take a small note on how the lock engages at the start of the function and unlocks when the upload ends.

 

 

USEFUL BITS & LINKS

That’s all for this tutorial, and here is a small section on some extras and links that may be useful to you.

 

HOW ABOUT THE SERVER-SIDE!?

It’s all up to you to handle the upload on the server-side, but if you are using PHP, here is a very simple upload handler:

dd-upload.php
<?php
$source = $_FILES["upfile"]["tmp_name"];
$destination = $_FILES["upfile"]["name"];
move_uploaded_file($source, $destination);
echo "OK";

 

COMPATIBILITY CHECKS

This drag-and-drop AJAX upload tutorial will work on all modern browsers.

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end of this tutorial. I hope that it has solved your upload woes, and if you have anything to add to this guide, please feel free to comment below. Good luck and happy coding!

36 thoughts on “Simple Drag-and-Drop File Upload With JS HTML5 (Free Download)”

  1. Hi. Awesome code, managed to change the uploads destination too. Is there a way to validate filesize and filetype with this script? 😎

    1. 1) As above – <input accept="FILE TYPES">.
      2) We can also use Javascript to check the file size before uploading – Will add these in a future update.
      3) But it’s better to do these checks on the server side.

  2. Hi there, and thank you for creating this very useful tool. I’ve just tried it out on a website I manage and it works perfectly. As expected, it uploads the files into the same directory that contains the dd-upload files.

    I’m wondering if it would be possible for me to modify dd-upload.js so as to specify a different destination folder for the uploads. Example: I currently have all the dd-upload files in a directory called “upload_test”, which lives in the website’s root (“htdocs”) folder. I would like the script to deposit the uploaded files into a separate folder (called “weekly audio”), which is also in the “htdocs” folder.

    I’m guessing that achieving this would involve me modifying/adding to the “C3 UPLOAD DATA” section, but I’m not sure what string of code to use. Whatever help you may be able to provide would be greatly appreciated.

    Many thanks,
    Ken Cumberlidge
    Norwich, UK

      1. Hi. Could you please expand on that reply. widget updated or code above?
        I’ve tried adding that code between various lines in the C3 category, but no luck.

  3. Thank you for this tutorial
    I am not familiar with AJAX so I don’t understand even thought everything else seems to work fine, when it is integrated to a larger form with the same file handling the upload ( form action=”” ), when submitted, the content of the form appears duplicated, inside the original page as many time as there files to upload. Just like if submitting form target was in a new iframe for each file, instead of loading in a blank page.
    Is there a way to prevent that and have the form behave more conventionally ?

  4. unbelievable and awesome.
    Thank you very much.
    There are tons of tutorials on the internet with incredibly complicated solutions,
    but I love this solution.
    It is presented in a simple and very understandable way.
    Many Thanks!

  5. I augmented the script to have only one line of output per file. Using ajax, you can update the line with the current status for each file!

    // OPTIONAL - SHOW UPLOAD STATUS under B
    ddup.thisdiv = document.getElementById(f.name);
    if (!ddup.thisdiv) {         
      // add new div with filename as id
      ddup.hstat.innerHTML += `${f.name} - Added to queue`;
    } else {  
      // change contents of existing div
      ddup.thisdiv.innerHTML = `${f.name} - Added to queue`;
    }
    
    // OPTIONAL - SHOW UPLOAD STATUS under C2
    ddup.thisdiv = document.getElementById(thisfile.name);
    ddup.thisdiv.innerHTML = `${thisfile.name} - Upload started`;
    
    // OPTIONAL - SHOW UPLOAD STATUS under C4
    ddup.thisdiv.innerHTML = `${thisfile.name} - ${this.response}`;
    
    // OPTIONAL - SHOW UPLOAD STATUS under xhr.onerror
    ddup.thisdiv.innerHTML = `${thisfile.name} - AJAX ERROR`;

Leave a Comment

Your email address will not be published.