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!
TABLE OF CONTENTS
JAVASCRIPT DRAG-AND-DROP UPLOAD
All right, let us now get into a demo and the details of how the drag-and-drop uploader works.
DRAG-DROP UPLOAD DEMO
<!-- (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
size : 100000, // optional, max upload size 100kb
types : ["jpg", "jpeg", "webp", "png", "gif"] // optional, allowed file extensions
});
</script>
For you guys who just want to use this as a “plugin”:
- Captain Obvious, load the CSS and Javascript.
- Define an empty
<div>
to generate the uploader. - 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.size
Optional, max allowed file size.types
Optional, allowed file extensions.
P.S. No actual upload will happen here, this demo will only show that drag-and-drop works.
PART A) INITIALIZE UPLOADER
// (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
instance.size = instance.size==undefined ? 0 : instance.size; // max upload size
instance.types = instance.types==undefined ? [] : instance.types; // allowed file types
for (let [k,v] of Object.entries(instance.types)) { instance.types[k] = v.toLowerCase(); }
// (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
andinstance.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
// (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) {
// (B1-1) CREATE HTML FILE ROW
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);
// (B1-2) FILE SIZE CHECK
if (instance.size!=0 && f.size>instance.size) {
f.hpercent.classList.add("bad");
f.hpercent.innerHTML = `Over upload limit of ${instance.size} bytes`;
}
// (B1-3) FILE TYPE CHECK
else if (instance.types.length>0 && !instance.types.includes(f.name.split(".").pop().toLowerCase())) {
f.hpercent.classList.add("bad");
f.hpercent.innerHTML = `File type not allowed`;
}
// (B1-4) CHECKS OK - ADD TO UPLOAD QUEUE
else { 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
, if it passes the checks.
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
// (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 = e => {
thisfile.hbar.style.width = 0;
thisfile.hpercent.innerHTML = "0%";
};
xhr.upload.onloadend = e => {
thisfile.hbar.style.width = "100%";
thisfile.hpercent.innerHTML = "100%";
};
xhr.upload.onprogress = e => {
percent = Math.ceil((e.loaded / e.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 of how the lock engages at the start of the function and unlocks when the upload ends.
DOWNLOAD & NOTES
Here is the download link to the example code, so you don’t have to copy-paste everything.
SORRY FOR THE ADS...
But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.
Buy Me A Coffee Code Boxx eBooks
EXAMPLE CODE DOWNLOAD
Click here to download. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
EXTRA 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:
<?php
$source = $_FILES["upfile"]["tmp_name"];
$destination = $_FILES["upfile"]["name"];
move_uploaded_file($source, $destination);
echo "OK";
FILE UPLOAD RESTRICTIONS
Yes, we can check the file size and extension in Javascript. But any experienced Code Ninja will be smart enough to open the developer’s console and change the Javascript. It is best to do the file checks on the server side as well.
COMPATIBILITY CHECKS
- Arrow Functions – CanIUse
- DataTransfer API – CanIUse
- XMLHttpRequest – CanIUse
This drag-and-drop AJAX upload tutorial will work on all modern browsers.
LINKS & REFERENCES
- HTML Drag and Drop API– MDN
- AJAX Beginner’s Tutorial – Code-Boxx
- Restrict Upload File Type In PHP – Code Boxx
- Restrict Upload File Size In PHP – Code Boxx
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!
Hello! Tank you very much, is a awesome script. I trying to add click function on the drag and drop zone to browse for a file but I lost… ¿Can anyone help? Excuse my poor english… Thanks!!
Hi. Awesome code, managed to change the uploads destination too. Is there a way to validate filesize and filetype with this script? 😎
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.