Convert Image to Data URI with JavaScript

By  on  

Whenever I go on a "performance run" on a website, the first place I look is imagery.  Why?  Because you can save an image out of Photoshop, push it into ImageOptim or even TinyPNG, and save 70% on its file size.  What do most developers not consider?  Taking tiny image files and making them data URIs instead of traditional images (another HTTP request).  Unfortunately that needs to happen on the CSS file before page load, but you need to get that data URI from somewhere, right?

I'm a bit suspicious of random websites which allow you upload files or content and return a given result;  you don't know the author of said code.  So I've gone to my own code, modifying it a bit along the way, to create a utility for converting an image to data URI!

Convert Image to Data URI

Like my original post, we need to rely on canvas to do the heavy lifting:

function getDataUri(url, callback) {
    var image = new Image();

    image.onload = function () {
        var canvas = document.createElement('canvas');
        canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size
        canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size

        canvas.getContext('2d').drawImage(this, 0, 0);

        // Get raw image data
        callback(canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, ''));

        // ... or get as Data URI
        callback(canvas.toDataURL('image/png'));
    };

    image.src = url;
}

// Usage
getDataUri('/logo.png', function(dataUri) {
    // Do whatever you'd like with the Data URI!
});

You could also set this up to use Promises instead of a callback.

Once the image has loaded, we thrust it into canvas and then export its data to a data URI.  In practical terms, this isn't a useful task since the image has already loaded but if you're looking to create a local utility to perform this task, here you go!

Recent Features

  • By
    7 Essential JavaScript Functions

    I remember the early days of JavaScript where you needed a simple function for just about everything because the browser vendors implemented features differently, and not just edge features, basic features, like addEventListener and attachEvent.  Times have changed but there are still a few functions each developer should...

  • By
    fetch API

    One of the worst kept secrets about AJAX on the web is that the underlying API for it, XMLHttpRequest, wasn't really made for what we've been using it for.  We've done well to create elegant APIs around XHR but we know we can do better.  Our effort to...

Incredible Demos

Discussion

  1. Notice this only works with images on the same domain.

    • …unless you setup the correct CORS headers and set the img.crossOrigin = "Anonymous" that is.

  2. I _think_ there are existing grunt, and gulp tasks that will automate this process for you, if you’re into that sort of thing:

    Gulp: https://www.npmjs.com/package/gulp-image-data-uri
    Grunt: https://github.com/ahomu/grunt-data-uri

    BTW, thanks for this post, I love site optimization. I generally just avoid images, or use SVGs, and I did not know about this.

    Saves connections, but it seems like it’s a trade off, as overall image size will allegedly increase by 37%: http://stackoverflow.com/questions/11402329/base64-encoded-image-size

    Nevertheless, I can think of numerous cases where I’d make that trade!

    • This example shows how to do things in real time in the browser. It’s a completely different use case than doing them within a build process like Gulp or Grunt.

  3. MaxArt

    Technically, if you remove the data:image/png;base64, part you’re not getting a data URI, but the image’s raw data converted in Base64 – which can be done with a simple AJAX request and window.btoa() if you’re not resizing it or changing its format.

    The good tricks of the canvas technique are resizing, changing format, adding simple captions and getting the first frame of an animated gif.

  4. Thanks for the write-up, David. How are you dealing with alt text in this situation?

    • The same way, alt text wouldn’t change.

    • Tim

      I guess I’m asking: where is the alt text in the HTML output if you’re spitting out a canvas element? As far as I know canvas doesn’t take alt text (I could be wrong)

    • sebas

      You are not using the canvas dude, the canvas is only to generate the URI, after that the uri will be used inside an IMG element

  5. Drew

    I like gulp-base64, which is similar gulp-image-data-uri. One nice thing it offers is the ability to set a cutoff size on images so you don’t end up with huge rasters in your css. It also lets you ignore certain files by regex.

  6. JamesG

    If only this would bring me closer to being able to have an image and imagemap as a single entity, to enable a single fetch across domain. Any thoughts welcome.

  7. Charles

    when changing canvas width and height the image is cropped, how to keep the image as is but change its width from 1024 to 600 ?

  8. GR

    Horrible function structure! You should remove the useless “callback” parameter and just return the data URI at the end of your function. And not to mention that “callback” is a special reserved word in some languages that can be embedded into JavaScript. ie: node.js.

    • How do you propose simply returning the data URI when it require the load event first? Had you argued using a Promise, I’d agree. But I’d love to see your version.

  9. As you mentioned in the end, the example is not terribly useful …but I like that you show people how to do things without starting by downloading 55 dependencies.

    When it comes to usefulness, this technique makes sense the other way around. On our site, we send images grabbed from canvas with toDataURL() back to the server to save them. We’re not sending them directly as base64 though – they are first converted to a Blob to save some space.

  10. Hi Man, I was just looking for this information. I was trying to store the dataURL into sessionStorage and it was not working. After implementing the image.load, it worked like charm :-). Thank you very much.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!