Mozilla

How to develop a HTML5 Image Uploader

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:

<input type="file" multiple>

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 ;)

40 comments

Comments are now closed.

  1. Mathias Bynens wrote on January 5th, 2011 at 04:16:

    Nice article! I wish there was a demo.

  2. stefs wrote on January 5th, 2011 at 06:01:

    i actually built a drag&drop image uploader similar to this several months ago, but abandoned the project when i wasn’t able to specify the jpeg quality.

    http://www.w3.org/TR/html5/the-canvas-element.html#dom-canvas-todataurl
    image/jpeg: The second argument, if it is a number in the range 0.0 to 1.0 inclusive, must be treated as the desired quality level. If it is not a number or is outside that range, the user agent must use its default value, as if the argument had been omitted.

    the point was, that a multi-megabyte hi-res jpeg file directly from the camera was about the same size as a resized png or jpeg (if you use jpeg as a format, default was 100% quality, making the file huge). so if the source files were, say, 3mb the resized versions were still about the same size, instead of 200kb.

    i’ve reported this bug here: https://bugzilla.mozilla.org/show_bug.cgi?id=564388 – not sure if it already works though.

  3. John Drinkwater wrote on January 5th, 2011 at 06:06:

    If only Firefox didn’t have a bug around file.type testing, it would be usable to me.
    Any files I have without extensions (‘.png’ etc) and it fails the test of being an image
    See https://bugzilla.mozilla.org/show_bug.cgi?id=596968

  4. Bohdan Ganicky wrote on January 5th, 2011 at 06:23:

    Hi,
    thanks for the article. I’ve already implemented something similar lately. It’s a private administration interface, so I could use all the new APIs. I’m really really happy that we can upload files via XHR now. I’m now looking for a way to somehow “augment” the jQuery .ajax() method to work with files.
    Here I’ve made a quick screen footage showing my implementation in action: http://www.youtube.com/watch?v=xSwAeJvZZ78

    You can either use the file input (multiple) or drag&drop images from file browser or desktop. Then images are sent to server via XHR (one by one) and server returns their processed representation. You can then click the image and make some editing and such.

    Works in Firefox 4 and latest “webkits”.

    1. Han Lin Yap wrote on January 6th, 2011 at 04:13:

      I have made a jQuery plugin that uses jQuery.ajax() to upload files.
      https://github.com/codler/jQuery-Ajax-Upload

  5. arno wrote on January 5th, 2011 at 06:27:

    Unfortunately, image resized with gecko usually appear quite blurry: Quality of gecko image resizer is not really good compared to resizers such as gimp, convert, phatch, whatever.

    For example, I took this image:
    http://upload.wikimedia.org/wikipedia/commons/0/00/Salar_de_Uyuni_D%C3%A9cembre_2007_-_Panorama_1_edit.jpg
    and I resized it to 800px width with gecko and with gimp. Here are the results.
    http://renevier.net/pics/salar_resize_with_gecko.jpeg
    http://renevier.net/pics/salar_resize_with_gimp.jpg

    you can try some more with this online resizer:
    http://renevier.net/misc/resizeimg.html

  6. JKM wrote on January 5th, 2011 at 07:06:

    The “Resize the image” logic is wrong. Consider a 1000×860 image: it’ll end up as 800×688. The “if (width > height) {” check is only valid if your maximum size is square. Otherwise you need to compare the aspect ratios (MAX_WIDTH/MAX_HEIGHT vs width/height).

    1. Mikkel wrote on January 5th, 2011 at 09:26:

      Easiest is to find the needed scale for each dimension, select the min value and then multiply that to all dimensions, ie (pseudo code):

      scale = min(max_height/height, max_width/width)
      if scale<1
      height *= scale
      width *= scale

  7. Richard wrote on January 5th, 2011 at 08:52:

    Thanks! So this should also work on iPhone Safari & Android Chrome as well?

  8. tehk wrote on January 5th, 2011 at 08:56:

    why would you keep image files w/o extensions anyway? expect everyone to do mime type checks on all your files? LOL

    1. thinsoldier wrote on January 5th, 2011 at 13:42:

      I see a lot of people in windows with file extensions turned off and in the past a lot of Mac users with more than a few images that had no extension in the file name.

  9. Jesper Kristensen wrote on January 5th, 2011 at 09:51:

    It would be nice if you could show a real useful example. For example: How do you feature detect for example drag&drop, so you can provide the fallback or even just the right instructions? Also copy&paste would be much more useful than drag&drop in most cases. How do you do that?

  10. Paul Irish wrote on January 5th, 2011 at 10:11:

    Awesome. I’ve had this on a shortlist.. Making drag ‘n drop file upload work everywhere is a priority.. and easy!

    Let me join the others asking for a demo. This is a common recipe it’ll good to have and iterate on.

  11. thinsoldier wrote on January 5th, 2011 at 13:40:

    In agreement with Jesper Kristensen. Many people I see using my computers(windows) and laptop(osx) struggle to even drag/drop cards in solitaire.

    The one computing skill they are all guaranteed to know is copy/paste!

    Even I prefer to copy/paste screenshots into mail.app rather than save them to a file, then locate and attach or drag them into mail.app. Sure it’ll be a huge .png attachment but it’s a faster workflow for me most of the time.

  12. Justin Dolske wrote on January 5th, 2011 at 14:45:

    I think Imgur (http://imgur.com/) is using something like this, though I’ll admit to not having actually looked at their code. :) Just go to the URL and drag’n’drop an image into the browser window.

    1. Paul Rouget wrote on January 6th, 2011 at 03:35:

      I’m actually working with the Imgur developer.

  13. Anonymous wrote on January 5th, 2011 at 14:50:

    Rather than a progress bar, use the image itself: draw a bright line moving down the canvas, as though “scanning” the image.

  14. Kai wrote on January 5th, 2011 at 15:07:

    It could look neat if you create a canvas element on the top of an already existing picture that is going to be replaced by the uploaded picture (image profile images) and have it resize vertically as the progress of the upload goes on. So at first the canvas would have a height of 0% and in the end a height of 100% covering the previous profile image.

  15. Daniel Goodwin wrote on January 5th, 2011 at 18:39:

    Continuing the postMessage mention, I’ve detailed how to adopt postMessage along side your existing APIs (using an image service as an example).

    http://dsg.posterous.com/setting-up-your-api-to-accept-html5-postmessa

  16. dimkalinux wrote on January 6th, 2011 at 10:47:

    I implemented HTML5 image uploader on http://pic.lg.ua/
    Works on Firefox4, WebKit.

  17. Johannes Fahrenkrug wrote on January 6th, 2011 at 11:58:

    Very nice article and nice touch with the sepia filter. Thanks!

  18. Ryan Gasparini wrote on January 6th, 2011 at 18:22:

    Why is Firefox implementing the window.URL object vs a global createObjectURL method?

  19. Brian Grinstead wrote on January 6th, 2011 at 22:11:

    Nice – I had implemented some of this file reading and image processing functionality a while ago in a wrapper called FileReader.js https://github.com/bgrins/filereader.js.

    After reading this article and all the comments about wanting a demo of the uploading, I thought I would throw one together. It accepts drag/drop or input selection, and will create a thumbnail for image types. Then you can choose to upload each file to the server.

    It definitely still needs some work, but I think it adds some missing functionality to the standard APIs as far as the file reading goes, and I would love to have any contributions or feedback.

    Demo:
    http://bgrins.github.com/filereader.js/

    If interested, you can also see the project I originally made it for: Instant Sprite – CSS Sprite Generator

  20. Loc wrote on January 13th, 2011 at 13:15:

    This is awesome. I know it works in Firefox, but does it work in IE, Chrome and Safari as well?

  21. Stephen Fluin wrote on January 15th, 2011 at 09:12:

    I seem to be running into an issue with this. It seems like the image isn’t fully loaded in the drop event. When I look at img.height or img.width, they are both 0, but if I wait (such as with an alert), and then check again, they are populated.

    Anyone else running into this?

  22. Simon wrote on January 15th, 2011 at 09:41:

    Any hints/ideas how to use the new blob interface for uploading (pause/resume) ?

  23. Hari wrote on February 25th, 2011 at 13:40:

    What would be the W3 standard for mozGetAsFile method?
    ” fd.append(“image”, canvas.mozGetAsFile(“foo.png”)); ”

    Or is this just a firefox only method to upload image?

    1. Paul Rouget wrote on February 26th, 2011 at 05:29:

      Just send the dataURL.

  24. Simon wrote on February 26th, 2011 at 01:29:

    @Hari img.src = canvas.toDataURL() ?

  25. Simon wrote on March 4th, 2011 at 04:59:

    I created a demo of multiple file uploader with drag and drop, that uses the slice method of new blob interface to allow you to pause/resume an upload. You can checkout the demo of the uploader or look at the feature list.

  26. Daryl wrote on April 23rd, 2011 at 18:57:

    this is interesting except I end up with huge image sizes. If I try to upload say a 53k image. I add it to the canvas resize it smaller. and then upload it to the server with canvas.mozGetAsFile and an XMLHttpRequest.

    when I post it to the server the file size is 900k

    Is there a quality setting somewhere I don’t know about? even at 100% that seems big for the size Image I have.

  27. stefs wrote on April 25th, 2011 at 15:51:

    @daryl: there is a quality setting in the spec, but few browsers support it yet. the firefox devs are working on it though (see https://bugzilla.mozilla.org/show_bug.cgi?id=564388 )

  28. CobaltBlueDW wrote on February 10th, 2012 at 11:43:

    Not only do I truly appreciate the effort you went to, to create this article. You also did an amazing job, which is rare in the vast fields of the inter-web.

    Great Job!

  29. CobaltBlueDW wrote on February 11th, 2012 at 19:57:

    For resizing in both directions with slightly less code. (Assume inputs have been validated):

    if(old.height/old.width – pref.height/pref.width > 0){
    new.width = pref.height/old.height * old.width;
    new.height = pref.height;
    }else{
    new.height = pref.width/old.width * old.height;
    new.width = pref.width;
    }

  30. Sabith Pocker wrote on April 9th, 2012 at 00:05:

    var reader = new FileReader();
    reader.onload = function(e) {
    img.src = e.target.result;
    delete this;
    }
    img.onload = function() {
    //things to do on image load
    }
    reader.readAsDataURL(file);
    Assigning dataURL doesnt actually loads the image, I tried working with the image inside reader onload, but the image did’nt load at that instant, above code can be useful to people using width and height of loaded image in their calculations.
    Great tutorial and great demo, thanks a lot Paul…

  31. Ashley Sheridan wrote on October 23rd, 2012 at 08:08:

    Stephen Fluin, I ran into this problem as well, but it’s easy to fix by putting your code inside an onload handler for the img object. It’s just that the browser needs a tiny bit of time to copy that image data across.

  32. OSP wrote on March 7th, 2013 at 05:46:

    Be aware of asynchronous image loading when setting image source and resizing imediatelly after image src set.

  33. mete kavruk wrote on April 5th, 2013 at 13:04:

    Everything seems fine in this tutorial to experienced coders here but not to a rookie one like me. I am trying to make a multiple file uploader without a drop zone but i can not find a working demo just to grab the code and modift as i have only modify capabilities.

    1. Sabith Pocker wrote on April 8th, 2013 at 14:13:

      Then you have 4 options. (as i thinketh)
      1. Look for instant code in other websites.
      2. Wait till some instant code is available.
      3. Go on and learn the codes and get out of the state of being a rookie.
      4. Hire an expert and get it done.

      Just to point out that you are neither lost nor are you a worthless rookie.
      Peace.

      1. mete kavruk wrote on April 9th, 2013 at 09:34:

        thanks for reply. The problem is that i have to be one man army for now as i am developing a unique project which i have to be the only one doing graphics, programming and marketing.So my capability to solve problems drops down.This sounds crazy to you but here in Turkey even big companies use ready scripts.For example The largest portals are a few years old 30 $ scripts. If you are a designer you are paid only 400 $ or max. 1500 $ for a month. Yes there are wealthy programmers but they are one in a thousand serving in very nieche sectors or the lucky ones reselling a ready script with some modifications for a fortune through at a few agencies.

Comments are closed for this article.