rasterizeHTML.js – Drawing HTML to the browser’s canvas

I wanted to have my HTML documents in a rasterized form and had a look at the browser’s canvas. It turns out that you can draw a lot of things with a canvas inside an HTML page, however you cannot easily draw an HTML page inside a canvas.

Digging a bit deeper I found Robert O’Callahan’s post on how to render HTML to a canvas by embedding the code inside an SVG image. There’s also some documentation back at the Mozilla Developer Network on how to achieve this based on the blog post.

The idea is pretty simple. SVG has a <foreignObject> element which allows mostly any HTML to be embedded. Such an SVG can then be easily drawn to the canvas using context.drawImage().

There is only one issue. Rendering SVGs is very restrictive. Loading of external resources is not allowed. The only way out is embedding CSS and images into the document. The latter by using data: URIs. If embedding of resources is done dynamically via JavaScript then there are further restrictions. Unless techniques such as CORS are used, you may only load content from a same origin.

Long story short, I sat down and started a small library that takes care of all the stuff that is needed to draw HTML to the canvas. Most of the code deals with finding elements in the DOM that need to be replaced, loading these resources and embedding them in the document. There are three convenience methods for drawing a DOM, an HTML string and/or a URL to the canvas easily.

Here’s a simple example how to use the code:

https://gist.github.com/2962400

After playing around with this for some days now I should mention that browser support seems a bit fragile. Firefox and Chrome are not consistent on rendering background images, and sometimes need a gental reload for doing so. Both Chrome and Safari have an issue with the origin-clean flag which made testing a bit more difficult. Stuff that turns up will be noted down in the wiki on Github. You can find the code here. I should probably file a few bug reports as a follow-up.

For me it was the first time dealing with a lot of asynchronous calls and it was fun to see how easy it was doable with JavaScript. Using JSHint and PhantomJS to run the Jasmine tests was easy and it just works. Also rasterizeHTML.js uses imagediff.js for testing that the results look just like the reference images. Travis CI makes sure I don’t break the build 🙂 What proved difficult during testing and also implementation was that all three browsers, Firefox, Chrome and Safari behaved differently (and basically also PhantomJS as a forth). This is especially interesting for the two WebKit-based browsers. Chrome supports the BlobBuilder interface, recently deprecated, while Safari is waiting for the official Blob specs to come. In some respect Chrome was more similar to Firefox than WebKit. One way of assuring full tests was to fallback to a simple manual test on Chrome and Safari for some code parts, due to said origin-clean flag.

Advertisements