7

Google Plus Style HTML5 Image Uploads

Posted (Updated ) in Javascript, PHP

If you’re reading this page (or my blog in general) it’s a pretty safe bet you already have Google+. If you’ve uploaded any photos to Google+ from Chrome or FF, you would also have noticed its snazzy HTML5 file uploader at work. This weekend I took it upon myself to whip up a quick and dirty version of that uploader and share its inner workings with the world.

Google+ Image Uploader
Google+ Image Uploader

Here’s a short video of my uploader at work:

 

Requirements:

  • PHP4+
  • HTML5-enabled browser (File API – including drag and drop, XHR2)
  • Some images to upload

Disclaimer: Currently only Firefox and Webkit based browsers meet the requirements above. Opera supports the File API and probably XHR2 (I haven’t tested), however it doesn’t have drag and drop so this tutorial won’t work with it. If you’re curious about whether or not IE will work with this tutorial, I’m already laughing at you.

Download the finished script here.

The comments pretty much make this tutorial self explanatory so I’ll let the code below speak for itself. You’ll need an index.php, upload.php and uploads folder writable to by apache.
index.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
<style>
	html, body {height:100%;margin:0px;padding:0px}
	#message_upload {font-size:26px;color:lightgrey;margin:15px}
	.box {float:left;width:150px;height:120px;background:lightgrey;margin:8px;text-align:center}
	.box .name {height:25px;margin-top:5px}
	.box .progresscontainer {height:5px;margin:80px 5px 0px}
	.box .progress {background:grey;height:5px;width:1%}
	.box.picture {background:none center top no-repeat;background-size:100%}
	.box.picture .progresscontainer, .box.picture .name {display:none}
</style>
<script type='text/javascript' src='http://code.jquery.com/jquery-1.6.2.min.js'></script>
<script type='text/javascript'>
	var tpl_box =
		"<div class='box'>
			<div class='name'></div>
			<div class='progresscontainer'><div class='progress'></div></div>
		</div>";
 
	$(document).ready(function() {
		// Check for the various File API support.
		if ( !window.File || !window.FileReader || !window.FileList || !window.Blob )
			alert('The File APIs are not fully supported in this browser. Please upgrade your browser.');
 
		//Listen for file drag and drop
		document.body.addEventListener('dragover', function(e) {
			e.stopPropagation();
			e.preventDefault();
		}, false);
		document.body.addEventListener('drop', function(e) {
			e.stopPropagation();
			e.preventDefault();
 
			upload_files( e.dataTransfer.files );
		}, false);
	});
 
	/*
	 * Upload a given FileList
	 */
	function upload_files( files )
	{
		$('#message_upload').hide();
 
		//Loop through dropped files, uploading only images and ignoring all others
		for (var i = 0, f; f = files[i]; i  )
				if ( f.type=='image/jpeg' || f.type=='image/png' || f.type=='image/gif' )
					upload_file( f );
				else
					alert( f.name   ' is not a valid image file.' );
	}
 
	/*
	 * Upload a given File
	 */
	function upload_file( f )
	{
		//Truncate long filenames
		var name = f.name;
		if ( name.length > 15 ) name = name.substr(0, 15) '...';
 
		//Create our new upload box to display
		var $box = $(tpl_box).find('.name').html( name ).end();
 
		//Do the actual uploading
		var XHR = new XMLHttpRequest();
		XHR.open('PUT', 'upload.php', true);
		//Send the file details along with the request
		for (var key in f)
		{
			var val = f[key];
 
			//This line is required for Firefox compatability
			if ( typeof(val) == 'string' || typeof(val) == 'number' )
				XHR.setRequestHeader('file_' key, val);
		}
		//Update our box's progress bar as the file uploads
		XHR.upload.addEventListener("progress", function(e) {
			if ( !e.lengthComputable) return;
 
			var percentComplete = parseInt(e.loaded / e.total * 100);
			$box.find('.progress').css('width', percentComplete   '%');
		}, false);
		//Display the uploaded pictures thumbnail once upload is complete
		XHR.onreadystatechange = function() {
			// in case of network errors this might not give reliable results
			if ( this.readyState == this.DONE )
				$box.addClass('picture').css('background-image', 'url(' escape(this.responseText) ')');
		}
		XHR.send( f );
 
		//Display the upload box
		$('body').append( $box );
	}
</script>
</head>
<body>
	<div id='message_upload'>Drag images here to upload</div>
</body>
</html>

upload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
	//http://php.net/manual/en/features.file-upload.put-method.php
	//http://stackoverflow.com/questions/541430/how-do-i-read-any-request-header-in-php
 
	$headers = apache_request_headers();
	//Webkit uses file_fileName, FF uses file_name
	$name_header = isset($headers['file_fileName']) ? $headers['file_fileName'] : $headers['file_name'];
 
	$upload_url = 'uploads/'.$name_header;
 
	/* PUT data comes in on the stdin stream */
	$putdata = fopen("php://input", "r");
 
	/* Open a file for writing */
	$fp = fopen(dirname(__FILE__).'/'.$upload_url, "w");
 
	/* Read the data 1 KB at a time and write to the file */
	while ($data = fread($putdata, 1024))
		fwrite($fp, $data);
 
	/* Close the streams */
	fclose($fp);
	fclose($putdata);
 
	echo $upload_url;
HTML5 Drag and Drop Upload
4 images uploading away
HTML5 Drag and Drop File Upload 2
3/4 uploaded and thumbnails showing

Simply drag image files into your browser window and they’ll upload away as they do in Google+.

Have fun with it!

Download the finished script here.