Simple Drag-and-Drop File Upload With JS and HTML5

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 HTML & CSS The Javascript
Useful Bits & Links The End

 

 

DOWNLOAD & NOTES

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, please feel free to comment below. I try to answer 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

Take note, no actual upload will happen here… This demo will only show that drag-and-drop works.

Drop Files Here

 

 

THE HTML & CSS

All right, let us now get started with the HTML and CSS of the drag-and-drop file uploader.

 

HTML LAYOUT

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="upzone">
  Drop Files Here
</div>
 
<!-- (C) UPLOAD STATUS -->
<div id="upstat"></div>
 
<!-- (D) FALLBACK -->
<form id="upform" action="dd-upload.php" method="post" enctype="multipart/form-data">
  <input type="file" name="upfile" accept="image/*" required>
  <input type="submit" value="Upload File">
</form>

Yep, there’s quite a bit going on here.

  • A – Self-explanatory… There is Javascript and some CSS involved.
  • B – An area to drop the files to start the upload.
  • C – Actually optional. Just to show the user on the upload status.
  • D – A fallback, traditional HTML upload form for the ancient browsers.

 

THE CSS

dd-upload.css
/* (A) UPLOAD ZONE */
#upzone {
  width: 300px; 
  height: 200px; 
  background: #cfd5ff;
  padding: 10px;
}
#upzone.highlight {
  background: #ff0;
}
 
/* (B) UPLOAD FORM */
#upform {
  display: none;
}

Just some simple cosmetics here for the “dropzone”. Take note – We will add the .highlight CSS class to the dropzone when a file hovers over it, and of course, we hide the fallback form by default.

 

 

THE JAVASCRIPT

With that, let us now move into the “heavyweight main Javascript upload engine”. As this is kind of confusing, I will explain section-by-section instead.

 

INITIALIZATION

dd-upload.js
var ddup = {
  // (A) ON PAGE LOAD
  hzone: null, // HTML upload zone
  hstat: null, // HTML upload status
  hform: null, // HTML upload form
  init : function () {
    // (A1) GET HTML ELEMENTS
    ddup.hzone = document.getElementById("upzone");
    ddup.hstat = document.getElementById("upstat");
    ddup.hform = document.getElementById("upform");
    
    // (A2) DRAG-DROP SUPPORTED
    if (window.File && window.FileReader && window.FileList && window.Blob) {
      // HIGHLIGHT DROPZONE ON FILE HOVER
      ddup.hzone.addEventListener("dragenter", function (e) {
        e.preventDefault();
        e.stopPropagation();
        ddup.hzone.classList.add('highlight');
      });
      ddup.hzone.addEventListener("dragleave", function (e) {
        e.preventDefault();
        e.stopPropagation();
        ddup.hzone.classList.remove('highlight');
      });
      
      // DROP TO UPLOAD FILE
      ddup.hzone.addEventListener("dragover", function (e) {
        e.preventDefault();
        e.stopPropagation();
      });
      ddup.hzone.addEventListener("drop", function (e) {
        e.preventDefault();
        e.stopPropagation();
        ddup.hzone.classList.remove('highlight');
        ddup.queue(e.dataTransfer.files);
      });
    }

    // (A3) DRAG-DROP UPLOAD NOT SUPPORTED
    else {
      ddup.hzone.style.display = "none";
      ddup.hform.style.display = "block";
    }
  }
}
window.addEventListener("DOMContentLoaded", ddup.init);

Not going to explain line-by-line, but in essence:

  • Captain Obvious, the var ddup object contains all the upload mechanics.
  • The init() function will fire on page load.
  • All it does are basically 3 things:
    • Fetch a reference to the HTML elements (drop zone, status, fallback form).
    • Attach a “drop-to-upload” if the drag-and-drop upload is supported.
    • Otherwise, this will hide the dropzone and show the fallback upload form instead.

 

 

UPLOAD QUEUE

dd-upload.js
var ddup = {
  // (B) UPLOAD QUEUE + HANDLER
  // NOTE: AJAX IS ASYNCHRONOUS
  // A QUEUE IS REQUIRED TO STOP SERVER FLOOD
  upqueue : [], // upload queue
  uplock : false, // currently uploading a file
  queue : function (files) {
    // FILE LIST INTO QUEUE
    for (let f of files) {
      // OPTIONAL - SHOW UPLOAD STATUS
      ddup.hstat.innerHTML += `<div>${f.name} - Added to queue</div>`;
      // ADD TO QUEUE
      ddup.upqueue.push(f); 
    }
    // GO!
    ddup.go();
  }
}

Yep, you sharp code ninjas should be wondering – Why don’t we immediately start the upload on drop? Why so dumb and roundabout to create an upload queue first? Well, because we will be using AJAX to upload, and it is asynchronous. If the user drops 100 files, that will upload 100 files in parallel and flood the server.

Nothing too complicated here actually, we are just keeping the dropped files into upqueue and using the uplock boolean flag to restrict one upload at a time.

 

AJAX UPLOAD

dd-upload.js
var ddup = {
   // (C) AJAX UPLOAD
  go : function () { if (!ddup.uplock && ddup.upqueue.length!=0) {
    // (C1) QUEUE STATUS UPDATE
    ddup.uplock = true;
 
    // (C2) PLUCK OUT FIRST FILE IN QUEUE
    let thisfile = ddup.upqueue[0];
    ddup.upqueue.shift();
    // OPTIONAL - SHOW UPLOAD STATUS
    ddup.hstat.innerHTML += `<div>${thisfile.name} - Upload started</div>`;
 
    // (C3) UPLOAD DATA
    let data = new FormData();
    data.append('upfile', thisfile);
    // ADD MORE POST DATA IF YOU WANT
    // data.append("KEY", "VALUE");
 
    // (C4) AJAX REQUEST
    let xhr = new XMLHttpRequest();
    xhr.open("POST", "dd-upload.php");
    xhr.onload = function () {
      // OPTIONAL - SHOW UPLOAD STATUS
      ddup.hstat.innerHTML += `<div>${thisfile.name} - ${this.response}</div>`;
      // NEXT BETTER PLAYER!
      ddup.uplock = false;
      ddup.go();
    };
    xhr.onerror = function(evt){
      // OPTIONAL - SHOW UPLOAD STATUS
      ddup.hstat.innerHTML += `<div>${thisfile.name} - AJAX ERROR</div>`;
      // NEXT BETTER PLAYER!
      ddup.uplock = false;
      ddup.go();
    };
    xhr.send(data);
  }}
};

Lastly, the 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";

 

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!

28 thoughts on “Simple Drag-and-Drop File Upload With JS and HTML5”

  1. 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 ?

  2. 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!

  3. 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. Required fields are marked *