- Drag and Drop Multiple Files Upload with HTML5, jQuery & FormData
- Very precise jQuery/Ajax Star Rating Plugin Tutorial
- jQuery editable grid system tutorial
- jQuery comment system tutorial
- MySQL, PHP, OOP database connection
- Ajax and jQuery autocomplete tutorial
- jQuery infinite category tree management system
Drag and Drop Multiple Files Upload with HTML5, jQuery & FormData
Drag & DropMultiple Files UploadHTML5jQueryFormDataXMLHttpRequest
HTML5 Drag and Drop Multiple File Uploader using jQuery and FormData
Introduction
In this tutorial I will show you how to build a drag and drop multiple images upload using HTML5 and jQuery. This tutorial follow the "Ajax upload multiple images using PHP and jQuery". You can easily mix both to allow your visitors to upload files using a drag and drop zone or the Browse Server button.
What do I use?
Bootstrap
Bootstrap default progress bar. If you want to use this progress bar you need to install Bootstrap CSS Framework. You may choose to use your own system or a simple waiting animated icon.
jQuery
The famous jQuery JavaScript Library which greatly simplifies JavaScript programming.
How do I proceed?
With XHR2, file upload through AJAX is supported. For example through FormData object, but be careful, it is not supported by all browsers, so also plan a standard File Upload with a Form.
FormData support starts from following desktop browsers versions:
![]() |
![]() |
![]() |
![]() |
![]() |
IE >= 10 | Firefox >= 4.0 | Chrome >= 7 | Safari >= 5 | Opera >= 12 |
How to send FormData objects with Ajax-requests in jQuery?
What is FormData?
The FormData object lets you compile a set of key/value pairs to send using XMLHttpRequest. Its primarily intended for use in sending form data, but can be used independently from forms in order to transmit keyed data. The transmitted data is in the same format that the form's
submit()
method would use to send the data if the form's encoding type were set to "multipart/form-data".
How to use FormData?
In this tutorial I use FormData with jQuery.
You can instantiate the FormData and then append fields to it by calling its append()
method, like this:
var fd = new FormData(); fd.append('file', files[0]); ...
Live Demo
Max: 128 Kb/image; jpg, jpeg, png & gif
Files architecture
ajax
jquery
js
upload
There is an "upload" folder. It's where images and thumbnails will be stored.
Database structure sample
id
is the "id" (database row number) of the image stored. Notice that I store the date and time when the image has been uploaded (not required of course), the original image and it's thumbnail.
CREATE TABLE IF NOT EXISTS `tc_tuto_upload_image` ( `id` int(6) NOT NULL AUTO_INCREMENT, `date_ins` date NOT NULL DEFAULT '0000-00-00', `hour_ins` time NOT NULL DEFAULT '00:00:00', `img_thumb` varchar(255) NOT NULL, `img_original` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 ;
Database connection
Page db.class.php. More information about the database connection I use here.
HTML code
This is the index.php page (or any page you want the script appears), I put the full page structure. JavaScript is at the end, before </body>
markup, with a link to jquery/jquery-1.10.1.min.js and a link to js/tuto-dd-upload-image.js.
Notice that I put the CSS style between <head></head>
. You can add it in an external CSS file...
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr"> <head> <title>Drag & Drop Multiple Files Upload</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" type="text/css"> <style> .dock { border: 4px dotted #cccccc; background-color: #ededed; width: 600px; height: 300px; color: #aaa; font-size: 18px; text-align: center; padding-top: 100px; } .dock_hover { border: 4px dotted #4d90fe; background-color: #e7f0ff; width: 600px; height: 300px; color: #4d90fe; font-size: 18px; text-align: center; padding-top: 100px; } </style> </head> <body> <div class="container"> <div class="row"> <div class="col-md-9"> <div class="progress"> <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">0%</div> </div> <div id="result"></div> <div align="center"> <div id="dock" class="dock">Drag & Drop Photos Here</div> </div> </div> </div> </div> <script type="text/javascript" src="jquery/jquery-1.10.1.min.js"></script> <script type="text/javascript" src="js/tuto-dd-upload-image.js"></script> </body> </html>
JS code
It's the tuto-dd-upload-image.js file. Lines are commented.
jQuery code is triggered when user drops files. Files are sent via an Ajax request to ajax/tuto-dd-upload-image.php.
Here is what happens in chronological order:
- User drags files over the zone ➜ change the color of the zone,
- If user leaves the zone ➜ the zone comes back to its original color,
- User drops the files ➜ progress bar at 0% ➜ test the files length ➜ launch the upload function,
- Upload function is launched with files argument ➜ create a new FormData object with one
append()
method per file dropped ➜ oneappend()
method for the number of files dropped, - Send
FormData()
to server using jQuery AJAX API ➜ calling ajax/tuto-dd-upload-image.php URL.
$(document).ready(function() { // Add eventhandlers for dragover and prevent the default actions for this event $('#dock').on('dragover', function(e) { $(this).attr('class', 'dock_hover'); // If drag over the window, we change the class of the #dock div by "dock_hover" e.preventDefault(); e.stopPropagation(); }); // Add eventhandlers for dragenter and prevent the default actions for this event $('#dock').on('dragenter', function(e) { e.preventDefault(); e.stopPropagation(); }); $('#dock').on('dragleave', function(e) { $(this).attr('class', 'dock'); // If drag OUT the window, we change the class of the #dock div by "dock" (the original one) }); // When drop the images $('#dock').on('drop', function(e){ // drop-handler event if (e.originalEvent.dataTransfer) { $('.progress-bar').attr('style', 'width: 0%').attr('aria-valuenow', '0').text('0%'); // Bootstrap progress bar at 0% if (e.originalEvent.dataTransfer.files.length) { // Check if we have files e.preventDefault(); e.stopPropagation(); // Launch the upload function upload(e.originalEvent.dataTransfer.files); // Access the dropped files with e.originalEvent.dataTransfer.files } } }); function upload(files){ // upload function var fd = new FormData(); // Create a FormData object for (var i = 0; i < files.length; i++) { // Loop all files fd.append('file_' + i, files[i]); // Create an append() method, one for each file dropped } fd.append('nbr_files', i); // The last append is the number of files $.ajax({ // JQuery Ajax type: 'POST', url: 'ajax/tuto-dd-upload-image.php', // URL to the PHP file which will insert new value in the database data: fd, // We send the data string processData: false, contentType: false, success: function(data) { $('#result').html(data); // Display images thumbnail as result $('#dock').attr('class', 'dock'); // #dock div with the "dock" class $('.progress-bar').attr('style', 'width: 100%').attr('aria-valuenow', '100').text('100%'); // Progress bar at 100% when finish }, xhrFields: { // onprogress: function (e) { if (e.lengthComputable) { var pourc = e.loaded / e.total * 100; $('.progress-bar').attr('style', 'width: ' + pourc + '%').attr('aria-valuenow', pourc).text(pourc + '%'); } } }, }); } });
PHP code
It's the ajax/tuto-dd-upload-image.php file, including a crop_img function, so for each image uploaded, I store its original size and a thumbnail (160x120 px or 120x160 px depending if it's a landscape or a portrait).
I also rename the original image name and its thumbnail as follow:
- I select a random number between 1,000,000 and 9,999,999 and store it in $randNbr variable
- The new original name is:
img-$randNbr-extension
- The thumbnail name is
img-small-$randNbr-extension
Lines are commented. You can easily adapt the script and remove what you don't need.
<?php include('../db.class.php'); $bdd = new db(); $acceptedExtension = Array('image/jpeg', 'image/jpg', 'image/pjpg', 'image/pjpeg', 'image/png', 'image/gif'); // add here allowed extensions $maxSize = 5000000; //image size max = 5Mb $destFolder = 'upload/'; echo '<div class="row">'; // Bootstrap CSS Thumbnails for($i = 0; $i < $_POST['nbr_files']; $i++) { // Loop through each file $imgType = $_FILES["file_".$i]["type"]; $imgSize = $_FILES["file_".$i]["size"]; $imgName = $_FILES["file_".$i]["name"]; $imgTmpName = $_FILES["file_".$i]["tmp_name"]; if (in_array($imgType, $acceptedExtension) && $imgSize <= $maxSize && $imgSize != "") { // we test the validity of the image $randNbr = rand(1000000, 9999999); // Choose a random number between 1000000 and 9999999 $newOriginalImageName = 'img-'.$randNbr.'.'.pathinfo($imgName, PATHINFO_EXTENSION); // Create a new file name including the random number, starting by img- $newThumbImageName = 'img-small-'.$randNbr.'.'.pathinfo($imgName, PATHINFO_EXTENSION); // Create a thumbnail name including the random number, starting by img-small- if(move_uploaded_file($imgTmpName,"../".$destFolder.$newOriginalImageName)) { // test if the original image is moved on the server copy("../".$destFolder.$newOriginalImageName, "../".$destFolder.$newThumbImageName); // we copy the origininal image and rename it (it will be our thumbnail) chmod ("../".$destFolder.$newThumbImageName, 0777); // we change the chmod so we can crop // we crop the photo list($width, $height, $type, $attr) = getimagesize("../".$destFolder.$newOriginalImageName); // we take the image height and width // the crop function is below if ($width>$height) { // if the image is landscape style crop_img ("../".$destFolder.$newThumbImageName, 160, 120); // we crop and resize 160x120px } else { // if the image is portrait style crop_img ("../".$destFolder.$newThumbImageName, 120, 160);// we crop and resize 120x160px } // we instert into database; the thumbnail path and the original path $upload = $bdd->execute('INSERT INTO tc_tuto_upload_image (date_ins, hour_ins, img_thumb, img_original) VALUES (NOW(), NOW(), "'.$newThumbImageName.'", "'.$newOriginalImageName.'")'); echo '<div class="col-xs-6 col-md-3"><a href="#" class="thumbnail"><img src="'.$destFolder.$newThumbImageName.'" alt="" /></a></div>'; // we send back the thumbnail - check Bootstrap for the CSS } } else { echo '<div class="col-xs-6 col-md-3"><a href="#" class="thumbnail">Error with your image (wrong format or size)!</a></div>'; } } echo '</div>'; // crop function (feel free to adapt) function crop_img ($image, $thumb_width, $thumb_height) { $filename = $image; $image = imagecreatefromstring(file_get_contents("$image")); $width = imagesx($image); $height = imagesy($image); $original_aspect = $width / $height; $thumb_aspect = $thumb_width / $thumb_height; if ( $original_aspect >= $thumb_aspect ) { // If image is wider than thumbnail (in aspect ratio sense) $new_height = $thumb_height; $new_width = $width / ($height / $thumb_height); } else { // If the thumbnail is wider than the image $new_width = $thumb_width; $new_height = $height / ($width / $thumb_width); } $thumb = imagecreatetruecolor($thumb_width, $thumb_height); // Resize and crop imagecopyresampled($thumb, $image, 0 - ($new_width - $thumb_width) / 2, // Center the image horizontally 0 - ($new_height - $thumb_height) / 2, // Center the image vertically 0, 0, $new_width, $new_height, $width, $height); return imagejpeg($thumb, $filename, 80); } ?>
Conclusion
Here we are, end of this tutorial, I hope it will help you .
Don't forget that drag'n'drop requires a HTML5 browser. That's pretty much all of them now, but not old versions.
You may also be interested by these two tutorials:
- How to upload an image using Ajax and jQuery, without refreshing page
- jQuery Ajax multiple file upload
If you have questions and need help, please comment below.
Happy sharing, happy coding!
Comments (6 comments)
@Mac
I have a JS error: "expected expression, got '<'
I think it comes from file tuto-dd-upload-images.js
where you added a variable $pk
Then you try to pass this variable here
ajax/tuto-dd-upload-image.php?pk=<?php echo $pk;?>
And of course it doesn't work.
But I think your problem is the same than Mr DaNNY below, and I gave him the way to pass an extra-argument in the ajax/tuto-dd-upload-image.php file.
Please have a look and tell me if it resolves your problem.
Its working fine on local server but when i put this online, it doesn't work at all.
Please have a look at this http://192.185.169.217/~ebichar/demo/multi/
Quick response will be highly appreciated.
Thanks.
Thanks Simon.. all works fine....(and thanks for the step by step comments, it really helps someone with slow brain :) )
@DaNNY
Thx for your interest in my script.
Here is the solution:
1) index.php file
Replace
<div id="dock" class="dock">Drag & Drop Photos Here</div>
By
<div id="dock" class="dock" rel='{"pid":<?php echo $pid; ?>}'>Drag & Drop Photos Here</div>
You add a rel tank with a JSON including your page PID, you can pass many other parameters :)
2) tuto-dd-upload-image.js file
Two modifications in this file
2.1)
Replace
// When drop the images
$('#dock').on('drop', function(e){
if (e.originalEvent.dataTransfer) {
$('.progress-bar').attr('style', 'width: 0%').attr('aria-valuenow', '0').text('0%');
if (e.originalEvent.dataTransfer.files.length) {
e.preventDefault();
e.stopPropagation();
// Launch the upload function
upload(e.originalEvent.dataTransfer.files);
}
}
});
By
$('#dock').on('drop', function(e){
var relData = $.parseJSON($(e.target).attr('rel'));
if (e.originalEvent.dataTransfer) {
$('.progress-bar').attr('style', 'width: 0%').attr('aria-valuenow', '0').text('0%'); // Bootstrap progress bar at 0%
if (e.originalEvent.dataTransfer.files.length) {
e.preventDefault();
e.stopPropagation();
// Launch the upload function
upload(e.originalEvent.dataTransfer.files, relData.pid);
}
}
});
So in fact you add the line:
var relData = $.parseJSON($(e.target).attr('rel'));
To get the "rel" attribute parameters
And you add the parameter relData.pid (your pid) to the upload function (to pass the pid parameter)
2.2)
Add the pid parameter to the function function upload(files){
So you will have
function upload(files, pid){
Inside this function:
Add fd.append('pid', pid);
After fd.append('nbr_files', i);
So you will have these 2 lines:
...
fd.append('nbr_files', i);
fd.append('pid', pid);
...
3) tuto-dd-upload-image.php file
There you get your PID with $_POST['pid']
For example you add the PID on the file name like that
$newOriginalImageName = 'img-'.$_POST['pid'].'-'.$randNbr.'.'.pathinfo($imgName, PATHINFO_EXTENSION);
Hope it helps and solve your problem :)
HI, great script (and with good comments throughout), however im trying to modify a little and getting a bit confused....
On the index page, i receive a dynamic variable ($pid = $_GET['id'];).
With this $pid i would like to add this value to each image in the database.
How do i pass this variable using the formdata through to echoing the value in the final php page?
Thanks in advance
This is one of the best tutorials on this subject that i have found. Thank you.
Yet there is one thing that bothers me - the progress bar. It seems like progresses to 100% at the very end of process. E.g. if i upload big number of images, the application does nothing for very long time, then suddenly the progress bar goes to 100% very fast. Should it be like that?