HTML5 comes with a set of really awesome APIs. If you combine these APIs with the <canvas> element, you could create a super/modern/awesome Image Uploader. This article shows you how.
All these tips work well in Firefox 4. I also describe some alternative ways to make sure it works on Webkit-based browsers. Most of these APIs don’t work in IE, but it’s quite easy to use a normal form as a fallback.
Please let us know if you use one of these technologies in your project!
Retrieve the images
Drag and drop
To upload files, you’ll need an <input type=”file”> element. But you should also allow the user to drag and drop images from the desktop directly to your web page.
I’ve written a detailed article about implementing drag-and-drop support for your web pages.
Also, take a look at the Mozilla tutorial on drag-and-drop.
Multiple input
Allow the user the select several files to upload at the same time from the File Picker:
Again, here is an article I’ve written about multiple file selection.
Pre-process the files
Use the File API
(See the File API documentation for details.)
From drag-and-drop or from the <input> element, you have a list a files ready to be used:
// from an input element
var filesToUpload = input.files;
// from drag-and-drop
function onDrop(e) {
filesToUpload = e.dataTransfer.files;
}
Make sure these files are actually images:
if (!file.type.match(/image.*/)) {
// this file is not an image.
};
Show a thumbnail/preview
There are two options here. You can either use a FileReader (from the File API) or use the new createObjectURL()
method.
createObjectURL()
var img = document.createElement("img");
img.src = window.URL.createObjectURL(file);
FileReader
var img = document.createElement("img");
var reader = new FileReader();
reader.onload = function(e) {img.src = e.target.result}
reader.readAsDataURL(file);
Use a canvas
Once you have the image preview in an <img> element, you can draw this image in a <canvas> element to pre-process the file.
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
Resize the image
People are used to uploading images straight from their camera. This gives high resolution and extremely heavy (several megabyte) files. Depending on the usage, you may want to resize such images. A super easy trick is to simply have a small canvas (800×600 for example) and to draw the image tag into this canvas. Of course, you’ll have to update the canvas dimensions to keep the ratio of the image.
var MAX_WIDTH = 800;
var MAX_HEIGHT = 600;
var width = img.width;
var height = img.height;
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
Edit the image
Now, you have your image in a canvas. Basically, the possibilities are infinite. Let’s say you want to apply a sepia filter:
var imgData = ctx.createImageData(width, height);
var data = imgData.data;
var pixels = ctx.getImageData(0, 0, width, height);
for (var i = 0, ii = pixels.data.length; i < ii; i += 4) {
var r = pixels.data[i + 0];
var g =pixels.data[i + 1];
var b = this.pixels.data[i + 2];
data[i + 0] = (r * .393) + (g *.769) + (b * .189);
data[i + 1] = (r * .349) + (g *.686) + (b * .168)
data[i + 2] = (r * .272) + (g *.534) + (b * .131)
data[i + 3] = 255;
}
ctx.putImageData(imgData, 0, 0);
Upload with XMLHttpRequest
Now that you have loaded the images on the client, eventually you want to send them to the server.
How to send a canvas
Again, you have two options. You can convert the canvas to a data URL or (in Firefox) create a file from the canvas.
canvas.toDataURL()
var dataurl = canvas.toDataURL("image/png");
Create a file from the canvas
var file = canvas.mozGetAsFile("foo.png");
Atomic upload
Allow the user to upload just one file or all the files at the same time.
Show progress of the upload
Use the upload events to create a progress bar:
xhr.upload.addEventListener("progress", function(e) {
if (e.lengthComputable) {
var percentage = Math.round((e.loaded * 100) / e.total);
// do something
}, false);
Use FormData
You probably don't want to just upload the file (which could be easily done via: xhr.send(file)
) but add side information (like a key and a name).
In that case, you'll need to create a multipart/form-data
request via a FormData
object. (See Firefox 4: easier JS form handling with FormData.)
var fd = new FormData();
fd.append("name", "paul");
fd.append("image", canvas.mozGetAsFile("foo.png"));
fd.append("key", "××××××××××××");
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://your.api.com/upload.json");
xhr.send(fd);
Open your API
Maybe you want to allow other websites to use your service.
Allow cross-domain requests
By default, your API is only reachable from a request created from your own domain. If you want to allow people use your API, allow Cross-XHR in your HTTP header:
Access-Control-Allow-Origin: *
You can also allow just a pre-defined list of domains.
Read about Cross-Origin Resource Sharing.
postMessage
(Thanks to Daniel Goodwin for this tip.)
Also, listen to messages sent from postMessage
. You could allow people to use your API through postMessage:
document.addEventListener("message", function(e){
// retrieve parameters from e.data
var key = e.data.key;
var name = e.data.name;
var dataurl = e.data.dataurl;
// Upload
}
// Once the upload is done, you can send a postMessage to the original window, with URL
That's all. If you have any other tips to share, feel free to drop a comment.
Enjoy ;)
About Paul Rouget
Paul is a Firefox developer.
40 comments