HTML5 File Uploads with jQuery

Posted by Martin Angelov on Sep 26th, 2011 in

Today we will be developing a small web application called Upload Center, that will allow people to upload photos from their computers by dragging and dropping them onto the browser window, possible with the new HTML5 APIs exposed by modern browsers.

The photos will have a preview and a progress bar, all of which controlled on the client side. Currently, the photos are only stored in a folder on the server, but you could improve it any way you like.

What are HTML5 File Uploads?

Uploading files using HTML5 is actually a combination of three technologies – the new File Reader API, the also new Drag & Drop API, and the good ol’ AJAX (with the addition of binary data transfer). Here is a description of a HTML5 file upload process:

  1. The user drops one or more files from their file system to the browser window by dragging. Browsers that support the Drag & Drop API will fire an event, which alongside other useful information, contains a list of files that were dropped;
  2. Using the File Reader API, we read the files in the list as binary data, and store them in memory;
  3. We use the new sendAsBinary method of the XMLHttpRequest object, and send the file data to the server.

Sounds complicated? Yes, it could use some optimization. Fortunately, there are jQuery plugins that can do this for us. One of them is Filedrop, which is a wrapper around this functionality, and provides features for limiting maximum file size and specifying callback functions, which is really handy for integrating it into your web applications.

Currently file uploads work only in Firefox and Chrome, but upcoming major versions of the other browsers also include support for it. A simple fallback solution for older browsers would be to display a regular file input dialog, but we won’t be doing this today, as we will be focusing our attention on using HTML5.

So lets get started!

The HTML

The markup of our Upload Center couldn’t be simpler. We have a regular HTML5 document, which includes our stylesheet and script.js file, the Filedrop plugin and the jQuery library.

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>HTML5 File Drag and Drop Upload with jQuery and PHP | Tutorialzine Demo</title>

        <!-- Our CSS stylesheet file -->
        <link rel="stylesheet" href="assets/css/styles.css" />

        <!--[if lt IE 9]>
          <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
    </head>

    <body>

		<header>
			<h1>HTML5 File Upload with jQuery and PHP</h1>
		</header>

		<div id="dropbox">
			<span class="message">Drop images here to upload. <br /><i>(they will only be visible to you)</i></span>
		</div>

        <!-- Including The jQuery Library -->
		<script src="http://code.jquery.com/jquery-1.6.3.min.js"></script>

		<!-- Including the HTML5 Uploader plugin -->
		<script src="assets/js/jquery.filedrop.js"></script>

		<!-- The main script file -->
        <script src="assets/js/script.js"></script>

    </body>
</html>

The only div that the Filedrop interacts with, is #dropbox. We will pass this element to the plugin, which will detect when a file is dropped on top of it. The message span is updated if there is an error condition (for example if your browser does not support one of the HTML5 APIs that this example relies on).

HTML5 File Upload Center with PHP and jQuery

HTML5 File Upload Center with PHP and jQuery

Later, when we drop a file, our jQuery code will display a preview by adding the following markup to the page:

<div class="preview done">

	<span class="imageHolder">
		<img src="" />
		<span class="uploaded"></span>
	</span>

	<div class="progressHolder">
		<div class="progress"></div>
	</div>

</div>

This snippet contains a preview of the image (the source attribute is going to be populated with a DataURL of the picture) and a progress bar. The whole preview can have the “.done” class, which causes the “.uploaded” span to show up (it is hidden by default). This span has the green check mark as its background, and indicates the upload is complete.

Great, lets move on to our script.js file!

The jQuery Code

As all of the actual file transfer functionality is handled by the Filedrop plugin, we only need to call it and pass a few callbacks, so we can hook it to our Upload Center. We will be writing a small PHP script that handles the uploads on the server in the next section.

The first step is to write a helper function that takes a file object (a special object which is created by the web browser on file drop, and has properties like file name, path and size), and creates the markup for previewing the upload.

assets/js/script.js

var template = '<div class="preview">'+
						'<span class="imageHolder">'+
							'<img />'+
							'<span class="uploaded"></span>'+
						'</span>'+
						'<div class="progressHolder">'+
							'<div class="progress"></div>'+
						'</div>'+
					'</div>'; 

	function createImage(file){

		var preview = $(template),
			image = $('img', preview);

		var reader = new FileReader();

		image.width = 100;
		image.height = 100;

		reader.onload = function(e){

			// e.target.result holds the DataURL which
			// can be used as a source of the image:

			image.attr('src',e.target.result);
		};

		// Reading the file as a DataURL. When finished,
		// this will trigger the onload function above:
		reader.readAsDataURL(file);

		message.hide();
		preview.appendTo(dropbox);

		// Associating a preview container
		// with the file, using jQuery's $.data():

		$.data(file,preview);
	}

The template variable holds the HTML5 markup of the preview. We get the DataURL of the image (a base64 encoded representation of the image bytes) and add it as the source of the image. Everything is then appended to the dropbox container. Now we are left with calling the filedrop plugin:

assets/js/script.js

$(function(){

	var dropbox = $('#dropbox'),
		message = $('.message', dropbox);

	dropbox.filedrop({
		// The name of the $_FILES entry:
		paramname:'pic',

		maxfiles: 5,
    	maxfilesize: 2, // in mb
		url: 'post_file.php',

		uploadFinished:function(i,file,response){
			$.data(file).addClass('done');
			// response is the JSON object that post_file.php returns
		},

    	error: function(err, file) {
			switch(err) {
				case 'BrowserNotSupported':
					showMessage('Your browser does not support HTML5 file uploads!');
					break;
				case 'TooManyFiles':
					alert('Too many files! Please select 5 at most!');
					break;
				case 'FileTooLarge':
					alert(file.name+' is too large! Please upload files up to 2mb.');
					break;
				default:
					break;
			}
		},

		// Called before each upload is started
		beforeEach: function(file){
			if(!file.type.match(/^image\//)){
				alert('Only images are allowed!');

				// Returning false will cause the
				// file to be rejected
				return false;
			}
		},

		uploadStarted:function(i, file, len){
			createImage(file);
		},

		progressUpdated: function(i, file, progress) {
			$.data(file).find('.progress').width(progress);
		}

	});

	var template = '...'; 

	function createImage(file){
		// ... see above ...
	}

	function showMessage(msg){
		message.html(msg);
	}

});

With this, every valid image file that is dropped on the #dropbox div gets uploaded to post_file.php, which you can see in the next section.

Upload Complete!

Upload Complete!

The PHP Code

On the PHP side of things, there is no difference between a regular form file upload and a drag and drop one. This means that you can easily provide a fallback solution to your application and reuse the same backend.

post_file.php

// If you want to ignore the uploaded files,
// set $demo_mode to true;

$demo_mode = false;
$upload_dir = 'uploads/';
$allowed_ext = array('jpg','jpeg','png','gif');

if(strtolower($_SERVER['REQUEST_METHOD']) != 'post'){
	exit_status('Error! Wrong HTTP method!');
}

if(array_key_exists('pic',$_FILES) && $_FILES['pic']['error'] == 0 ){

	$pic = $_FILES['pic'];

	if(!in_array(get_extension($pic['name']),$allowed_ext)){
		exit_status('Only '.implode(',',$allowed_ext).' files are allowed!');
	}	

	if($demo_mode){

		// File uploads are ignored. We only log them.

		$line = implode('		', array( date('r'), $_SERVER['REMOTE_ADDR'], $pic['size'], $pic['name']));
		file_put_contents('log.txt', $line.PHP_EOL, FILE_APPEND);

		exit_status('Uploads are ignored in demo mode.');
	}

	// Move the uploaded file from the temporary
	// directory to the uploads folder:

	if(move_uploaded_file($pic['tmp_name'], $upload_dir.$pic['name'])){
		exit_status('File was uploaded successfuly!');
	}

}

exit_status('Something went wrong with your upload!');

// Helper functions

function exit_status($str){
	echo json_encode(array('status'=>$str));
	exit;
}

function get_extension($file_name){
	$ext = explode('.', $file_name);
	$ext = array_pop($ext);
	return strtolower($ext);
}

The script runs some checks on the HTTP method that was used to request the page and the validity of the file extension. Demo mode is mainly for demo.tutorialzine.com, where I don’t want to store any file uploads (if you don’t call move_uploaded_file in your script, the file is deleted automatically at the end of the request).

Now lets make it pretty!

The CSS Styles

I left out the parts of the stylesheet that are not directly related to the uploads. You can see everything in styles.css.

assets/css/styles.css

/*-------------------------
	Dropbox Element
--------------------------*/

#dropbox{
	background:url('../img/background_tile_3.jpg');

	border-radius:3px;
	position: relative;
	margin:80px auto 90px;
	min-height: 290px;
	overflow: hidden;
	padding-bottom: 40px;
    width: 990px;

	box-shadow:0 0 4px rgba(0,0,0,0.3) inset,0 -3px 2px rgba(0,0,0,0.1);
}

#dropbox .message{
	font-size: 11px;
    text-align: center;
    padding-top:160px;
    display: block;
}

#dropbox .message i{
	color:#ccc;
	font-size:10px;
}

#dropbox:before{
	border-radius:3px 3px 0 0;
}

/*-------------------------
	Image Previews
--------------------------*/

#dropbox .preview{
	width:245px;
	height: 215px;
	float:left;
	margin: 55px 0 0 60px;
	position: relative;
	text-align: center;
}

#dropbox .preview img{
	max-width: 240px;
	max-height:180px;
	border:3px solid #fff;
	display: block;

	box-shadow:0 0 2px #000;
}

#dropbox .imageHolder{
	display: inline-block;
	position:relative;
}

#dropbox .uploaded{
	position: absolute;
	top:0;
	left:0;
	height:100%;
	width:100%;
	background: url('../img/done.png') no-repeat center center rgba(255,255,255,0.5);
	display: none;
}

#dropbox .preview.done .uploaded{
	display: block;
}

/*-------------------------
	Progress Bars
--------------------------*/

#dropbox .progressHolder{
	position: absolute;
	background-color:#252f38;
	height:12px;
	width:100%;
	left:0;
	bottom: 0;

	box-shadow:0 0 2px #000;
}

#dropbox .progress{
	background-color:#2586d0;
	position: absolute;
	height:100%;
	left:0;
	width:0;

	box-shadow: 0 0 1px rgba(255, 255, 255, 0.4) inset;

	-moz-transition:0.25s;
	-webkit-transition:0.25s;
	-o-transition:0.25s;
	transition:0.25s;
}

#dropbox .preview.done .progress{
	width:100% !important;
}

The .progress div is positioned absolutely. Changing its width (in percent) makes for a natural progress indicator. Throw in a 0.25 transition, and you have animated increments which would be a bit tricky to do with jQuery alone.

With this our HTML5 Upload Center is complete!

We’re done!

You can use this as a starting point for a file upload service, HTML5 gallery, file manager or your app’s new admin panel. Add your thoughts or suggestions in the comments.

Tweet to download! Download Photoshop PSD

Related Tutorials

  • Apple-like Login Form with CSS 3D Transforms
  • Enhance Your Website with the FullScreen API
  • Question of the Day with CodeIgniter and MySQL
  • Making a jQuery Countdown Timer

63 Comments

  1. Fabio says:

    …very nice app, tnx ! Only thing is that i personally still prefer an “old school” select button instead of drag and drop for file selection before upload… but i guess it won’t be too difficult modifying the code a little bit to achieve this…

    1. Martin Angelov says:

      The same PHP script can be used to handle both regular and drag and drop uploads, so you only have to show a form with a file input box to make it work.

      1. guster says:

        yes thanks !

  2. andrei says:

    An interesting article well worth reading!

  3. Codeforest says:

    Very nice tutorial.

    But, demo is not working for me in FireFox 6.0.2.

    1. Martin Angelov says:

      Quite a few people are testing the demo at the moment, so it is probably something temporary with my webhost. It is tested and working in Firefox 6.0.2.

      I am planning to move the site to a dedicated vps over the weekend, so I hope everything will work as it should from then on.

  4. Marco says:

    Congratulations, really a nice tutorial. How do I add the category name in the database column when loading? is it possible?

  5. Guillaume says:

    Really nice scripts and also beautiful design, Thanks.

  6. Chris says:

    I have this form working but it only seems to work for your PHP script. My company uses an external upload handler which requires the name of the input file field be ‘data’, a separate field for image caption (the name of this field must be ‘caption’), and the form’s enctype must be ‘multipart/form-data. Can I still use this uploader to achieve what I am attempting?

    1. Martin Angelov says:

      You will have to change the upload process. Maybe add a caption after the upload is complete?

  7. Alberto says:

    Well, what to say?

    You are simply awesome, you are also an amazing designer.

    Good job Martin!

  8. PhilD says:

    I was thinking to do this but you beat me to it. Great work. (:

  9. Viktor says:

    It does not work in Opera.

    1. Martin Angelov says:

      Only Firefox and Chrome for now. As the script depends on the file upload APIs and not on specific browsers, once Opera adds support it would work automatically.

  10. Srinath says:

    hello, gr8 tutorial like it.
    wondering, how can i change it to accept remaining file extensions like zip, pdf, doc, txt?

    i tried to add them in the script.js but error keep poppin up with image upload only…
    thank you

    1. Martin Angelov says:

      You will need to add them in the PHP file as well (in the array with accepted extensions). However the script will attempt to generate previews for them, so you will probably want to display some generic image instead.

      1. Srinath says:

        thank you

        i tried to add this or replace in the post_file.php
        $allowed_ext = array(‘jpg’,'jpeg’,'png’,'gif’,'doc’,'docx’,'pdf’,'xls’,'xlsx’,'pptx’,'ppt’,'rtf’,'txt’,'c’,'css’)

        but still, i get the image only error, when i tried to delete the javascript check image part, its not uploading either.. could you please further help me? thank you very much in advance.

        1. In script.js file comment out lines 35 – 44

  11. Olivier says:

    You are just…. awesome!

    Thank you for this tutorial, very nice one as usual. And as Alberto said, even for graphic design you are good.

  12. Great work team, I really appreciate the efforts, well I was just thinking to ask you am I allowed to use this for my personal use but I read your License thank you very much

  13. izzy chuks says:

    nice tuts, wud love to give it a try, my question is how do you delete/remove an uploaded file?

    1. Martin Angelov says:

      I am afraid you will have to do it yourself. The script only demonstrates how HTML5 uploads work, it doesn’t handle other operations.

  14. You can improve your PHP code.
    First, get_extension() function is useless: just use pathinfo($file_name, PATHINFO_EXTENSION)
    Next, exit_status() is not HTTP-friendly: you should send a proper header (e.g. 405 for method not allowed) along with error message

    1. Martin Angelov says:

      Great suggestions! Had forgotten about pathinfo.

  15. A reader says:

    This upload process is insecure. Putting all your trust on the file extension is a bad thing to do.

  16. Sharun Kumar says:

    Hey the PSD file is not available :/

  17. Chris says:

    I’m not sure if i’m having some sort of directory problem, but when i try and upload a file it progresses to about half way then stops. Anyone know why this could be?

    1. Martin Angelov says:

      The best way to find out is to install Firebug and inspect the request. It will give you an error code or some other indication on what might be causing the problem.

  18. Erik says:

    This is great stuff. Im wondering if it’s possible to add a normal “select file” field to this and combine the two? Also, I’m wondering if it’s possible to add the files to a queue instead of start the upload straight away, and then add an “upload” button?

    Cheers

  19. Chris says:

    This is the error i am getting

    Uncaught SyntaxError: Unexpected token / jquery-1.6.3.min.js:2
    e.extend.parseJSON jquery-1.6.3.min.js:2
    xhr.onload filedrop.js:223

    Is the start time of the download screwing up the upload process?

    1. Chris says:

      Ah, i have managed to sort it

      Cheers

    2. paul says:

      In case anybody runs into this same problem, its a permissions issue.

  20. marineking says:

    always learning, nice job,orz

  21. Magnus says:

    Could this be altered as to send files directly to a specified FTP folder on another FTP server than the website is hosted on?

    I haven’t got the programming skills, but I’m very interested in the potential of this file uploader.

    1. Martin Angelov says:

      I am afraid this is not possible. You will first need to transfer it to your server and then send it using PHP. Take a look at the FTP extension – it should be available in your installation.

  22. Miingno says:

    I have a strange problem, the upload works fine, the files appear on the server but the progress bar doesn’t show up at all or gets stuck at about 20%
    CSS is linked correctly.
    Anyone may know what could cause that?

    1. Martin Angelov says:

      The best way to pinpoint the problem is to inspect the upload with firebug – it will show you if and what errors are thrown on the server side.

      1. Miingno says:

        Found the error: my web server had json disabled!
        Enabled and works fine now.

  23. Darrell says:

    You Sir are a saviour. I love this and it has solved a previous headache I was having with FancyUpload where I struggled to get the POST variables sending through with the Flash POST.

    In this case I modified your dropbox.filedrop({ statement to add the data: { param1: function(){ blah blah } to get the value of a form select dropdown so that I could pass that with each filedrop. (as per weixiyen usage example)

    Thanks

  24. Malcolm says:

    Absolutely awesome work dude! Got it to work a treat on local server using multiple file types. Now have to find out how :
    (1) Use generic previews for .pdf and .doc files
    (2) Set file size limits
    Keep up the great work

  25. SitiWeb says:

    Good tutorial! Thanks!

  26. Burhan says:

    Good Totorial for me…thaks.

  27. Chris says:

    Hi Martin,

    Excellent post and demo. Thank you for sharing! I have implemented this in my application but would like your, or the community’s, suggestion on how to handle the deletion of one or more files as user are prone to uploading wrong stuff. Options I have considered include:

    1. Have upload REST API return a file identifier (uploadFinished) and bind that identifier to a href “delete” image. When clicked, delete file on the server.
    2. Keep filename unchanged on both client and server. Allow deletion of file (uploadFinished) using filename only, similar to #1.

    If nether of these options are plausible, what have you and other done? The delete feature will make this tutorial more meaningful for real, everyday usage.

    Thanks,

    Chris

    1. Martin Angelov says:

      It would be best to have a real database behind it. Users should have some kind of identifier (you could use IP addresses, but it wouldn’t be as reliable as a real registration). After a file is uploaded, you should store it on your server with unique name and insert a record in your files table. You should then return this name (or the Id of the file record) with the response and display a delete link as in your original idea. Clicking on the link would send an AJAX POST request to a backend script, which would validate if the file exists and the user is allowed to delete it.

      This way users will only be able to delete files they uploaded themselves.

      1. Chris says:

        Exactly. Do you think you will extend this demo with the delete feature – I did send you some sample code. Or should I just extend this feature and discuss it on my own blog? Just giving courtesy by asking you, the original author. :)

  28. Wayne says:

    Could this be modified to allow dropping a entire folder containing the images?

    Thank you

    1. Martin Angelov says:

      The last time I checked, uploading entire folders with files is only supported in Google Chrome, so there won’t be proper cross-browser support for some time to come.

  29. Bogdan says:

    Hy! nice tut. is there a way to download a final result of a drag and drop picture over picture? I am thinking of making a composition of several photos, one close to each other, or even one picture on top of other picture and in final the user can download his photo composition. Could you tell me if it can be done in the same way like drag and drop picture upload?

  30. itoctopus says:

    Hi Martin,

    Kudos for the great work you did on this. This is unbelievable! I have tried it with multiple images and it worked like a charm!

  31. Becky says:

    Hi!

    Really G-R-E-A-T job!!!!!!!!! It saved me a lot!!!! Thank you very much!!!!
    xoxoxoxox

  32. RITESH says:

    That is so nice and use full.
    thanks tutorialzine.com

  33. Mike says:

    HTML 5 is on of the best things that have happened to web design in a while. Of course you could say that CSS made a bigger difference but for me HTML5 is better because we don’t need flash or any other add-ons to play videos or any cool looking effects.
    If we speak about HTML5 upload script I installed it on my translation service website, so people can now attach files with it and it replaced my flash based uploader.
    Thanks! Much appreciate your effort!

  34. benno_007 says:

    Epic work on this uploader mate.

    For those who want to degrade to a normal file upload, just set up your form by default to use a file upload input box with a class ‘jquery-filedrop’. Then, wrap the plugin code with this condition: if(!!FileReader && Modernizr.draganddrop)
    It basically says: if you’re capable of using this code, go ahead and use it. Otherwise, it’ll stick you with your normal uploader.

    E.G.:

    // If they can use filereader and have drag and drop (would require modernizr for that part)
    if(!!FileReader && Modernizr.draganddrop) {
    
        (function($){
    
    	jQuery.event.props.push("dataTransfer");
            [..snip..]
        })(jQuery);
    
        $(function() {
    
          // ADD this code:
          // fuparent assumes the parent element will be replaced with the dropbox e.g. I use Zend Form, so the parent of the file input is a dd. Just wrap it in anything-div, span, whatever.
    
          var fileupload = $('.jquery-filedrop'), fuparent = fileupload.parent(), count = fileupload.length;
          fileupload.remove();
          fuparent.html('Drop images here to upload. <i>(they will only be visible to you)</i>');
    
         var dropbox = $('#dropbox'), ... [..snip..]
    
         // count can then be used to set the maxfiles and FileTooLarge error (saves you handling that in two places).
      }
    }
    
  35. Abid says:

    I cant seem to find out how to link this to dropbox? There is a part in the php file:

    $upload_dir = ‘uploads/’;

    But where do I get the upload directory from? I have tried to use an FTP server but no luck??

  36. David says:

    Hi,
    Great tutorial!

    Can I set the URL in dropbox.filedrop setting (script.js) to an external domain like this ?

    dropbox.filedrop({
    …..
    maxfiles: 5,
    maxfilesize: 2,
    url: ‘http://www.website.ext/post_file.php’,
    ….
    },

    thanks tutorialzine.com

    1. Martin Angelov says:

      You can’t. This would be blocked by the browser’s security policy.

  37. Anders says:

    Works like a charm! How ever – is there an easy way to pass along some kind of variable that in the end could be used to decide which upload directory the images will end up in?

    I mean: making the index.html file as a php file like index.php?dir=4, select the variable and pass it through the js file and add it ” url: ‘post_file.php’,”, like ” url: ‘post_file.php?dir=4′, and then use the variable in post_file.php.

    I’m useless when it comes to js, but can do the php adjustments.

  38. Peter says:

    Hi there. It is uploading the files okay, however it won’t show the status bar and Firebug reports this:

    $.data(file).find(‘.progress’).width(progress); is not a function

    and this

    JSON.parse: unexpected character

    ….resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d…

    Help would be appreciated, thanks :)

  39. Sebastian says:

    Does anyone know how to fix the 4 MB max size limit?
    I have changed the max post size and everything, so I’m pretty sure this is HTML5′s fault.

    Thanks!

    Btw, you can test the bug on my website.

    1. Sebastian says:

      Whoops, I just realized I changed max_post_size instead of max_upload_size.
      It works great now, I was wondering if there was a way to add a file browser so browsers that don’t support drag and drop will also be able to upload?

      Thanks!

  40. Thanks, very nice.
    I’m porting for ASP.NET MVC. :D

  41. Savo says:

    Can you modify this code for scaling the image and rename the file upload for example thum_image1.jpg and upload the original file.

    Many Thanks

    From Quito – Ecuador

  42. For those who want to be able to upload a PDF in addition to an image (as I did), do the following:

    1) In post_file.php, add ‘pdf’ to the allowed_ext array
    2) In script.js, change the beforeEach function (line 36) to this:
    if(!file.type.match(/^image\//) && !file.type.match(/^application\//)){
    alert(‘Only images and pdfs are allowed!’);

    // Returning false will cause the
    // file to be rejected
    return false;
    }
    3) Also in script.js, in the createImage function, change the reader.onLoad section to this:
    reader.onload = function(e){

    // e.target.result holds the DataURL which
    // can be used as a source of the image:
    if (file.type.match(/^application\//)){
    image.attr(‘src’,'path/to/your/thumbnail’);
    }
    else {
    image.attr(‘src’,e.target.result);
    }
    };
    where path/to/your/thumbnail is… the path to the image you want to use when someone uploads a pdf.

    Note that this might not be too safe since matching "application" allows more than pdf, so any suggestions to improve this are welcome.

Subscribe for the comments on this postAdd Comment

Add a Reply

HTML is escaped automatically. Surround code blocks with <pre> for readability.
Perks:   **bold**   __italics__   [link](http://example.com)