Convert Image to Data URI with JavaScript
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!
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.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.
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 andwindow.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.
Thanks for the write-up, David. How are you dealing with alt text in this situation?
The same way, alt text wouldn’t change.
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)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
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.
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.
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 ?
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.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.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.