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:


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.

More articles by Paul Rouget…


40 comments

  1. Mathias Bynens

    Nice article! I wish there was a demo.

    January 5th, 2011 at 04:16

  2. stefs

    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.

    January 5th, 2011 at 06:01

  3. John Drinkwater

    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

    January 5th, 2011 at 06:06

  4. Bohdan Ganicky

    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”.

    January 5th, 2011 at 06:23

    1. Han Lin Yap

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

      January 6th, 2011 at 04:13

  5. arno

    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

    January 5th, 2011 at 06:27

  6. JKM

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

    January 5th, 2011 at 07:06

    1. Mikkel

      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

      January 5th, 2011 at 09:26

  7. Richard

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

    January 5th, 2011 at 08:52

  8. tehk

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

    January 5th, 2011 at 08:56

    1. thinsoldier

      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.

      January 5th, 2011 at 13:42

  9. Jesper Kristensen

    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?

    January 5th, 2011 at 09:51

  10. Paul Irish

    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.

    January 5th, 2011 at 10:11

  11. thinsoldier

    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.

    January 5th, 2011 at 13:40

  12. Justin Dolske

    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.

    January 5th, 2011 at 14:45

    1. Paul Rouget

      I’m actually working with the Imgur developer.

      January 6th, 2011 at 03:35

  13. Anonymous

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

    January 5th, 2011 at 14:50

  14. Kai

    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.

    January 5th, 2011 at 15:07

  15. Daniel Goodwin

    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

    January 5th, 2011 at 18:39

  16. dimkalinux

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

    January 6th, 2011 at 10:47

  17. Johannes Fahrenkrug

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

    January 6th, 2011 at 11:58

  18. Ryan Gasparini

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

    January 6th, 2011 at 18:22

  19. Brian Grinstead

    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

    January 6th, 2011 at 22:11

  20. Loc

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

    January 13th, 2011 at 13:15

  21. Stephen Fluin

    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?

    January 15th, 2011 at 09:12

  22. Simon

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

    January 15th, 2011 at 09:41

  23. Hari

    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?

    February 25th, 2011 at 13:40

    1. Paul Rouget

      Just send the dataURL.

      February 26th, 2011 at 05:29

  24. Simon

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

    February 26th, 2011 at 01:29

  25. Simon

    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.

    March 4th, 2011 at 04:59

  26. Daryl

    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.

    April 23rd, 2011 at 18:57

  27. stefs

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

    April 25th, 2011 at 15:51

  28. CobaltBlueDW

    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!

    February 10th, 2012 at 11:43

  29. CobaltBlueDW

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

    February 11th, 2012 at 19:57

  30. Sabith Pocker

    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…

    April 9th, 2012 at 00:05

  31. Ashley Sheridan

    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.

    October 23rd, 2012 at 08:08

  32. OSP

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

    March 7th, 2013 at 05:46

  33. mete kavruk

    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.

    April 5th, 2013 at 13:04

    1. Sabith Pocker

      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.

      April 8th, 2013 at 14:13

      1. mete kavruk

        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.

        April 9th, 2013 at 09:34

Comments are closed for this article.