Weston Ruter

Web application developer in Portland, Oregon

CSS Gradients via Canvas

By

In a current project at Shepherd Interactive, certain page elements were designed with background gradients. Given the desire to minimize the need for externally-loaded background images wherever possible, I thought this would be a great opportunity to play around with WebKit's proposed CSS Gradients, which are natively supported by Safari, Chrome, and other WebKit-based browsers. In being a WebKit proposal, however, CSS Gradients are not (yet) natively supported in other rendering engines as used by Firefox, Opera, and Internet Explorer.

CSS Gradients via Canvas provides a subset of WebKit's CSS Gradients proposal for browsers that implement the HTML5 canvas element. To use, just include css-gradients-via-canvas.js (12KB) anywhere on the page (see examples below). Unlike WebKit, this implementation does not currently allow gradients to be used for border images, list bullets, or generated content. The script employs document.querySelectorAll()—it has no external dependencies if this function is implemented; otherwise, it looks for the presence of jQuery, Prototype, or Sizzle to provide selector-querying functionality.

The implementation works in Firefox 2/3+ and Opera 9.64 (at least). Safari and Chrome have native support for CSS Gradients since they use WebKit, as already mentioned. Beginning with version 3.6, CSS Gradients are also natively supported by Firefox and this implementation will defer in such case; note that you will need to specify two separate background CSS properties, one with -webkit-gradient and another with -moz-linear/radial-gradient which has a different syntax). This implementation does not work in Internet Explorer since IE does not support Canvas, although IE8 does support the data: URI scheme, which is a prerequisite (see support detection method). When/if Gears's Canvas API fully implements the HTML5 canvas specification, then this implementation should be tweakable to work in IE8. In the mean time, rudimentary gradients may be achieved in IE by means of its non-standard gradient filter.

CSS Gradients via Canvas works by parsing all stylesheets upon page load (DOMContentLoaded), and searches for all instances of CSS gradients being used as background images. The source code for the external stylesheets is loaded via XMLHttpRequest—ensure that they are cached by serving them with a far-future Expires header to avoid extra HTTP traffic. The CSS selector associated with the gradient background image property is used to query all elements on the page; for each of the selected elements, a canvas is created of the same size as the element's dimensions, and the specified gradients are drawn onto that canvas. Thereafter, the gradient image is retrieved via canvas.toDataURL() and this data is supplied as the background-image for the element.

A few notes regarding interactivity with this implementation: CSS gradients will not be applied to elements dynamically added after DOMContentLoaded. Additionally, each element that has a CSS gradient applied to it gets assigned a method called refreshCSSGradient(); at any time, this method may be invoked to redraw the gradient on a given element. This is especially useful (and necessary) when an element's size dynamically changes, for example as the result of some user interaction. Likewise, it is important to note that it will not work to rely on event handlers to invoke refreshCSSGradient() on elements whose style is changed by CSS rules with pseudo-selectors like :hover and :active; this is because event handlers are fired before the rule's style changes are applied to the element. Toggling an element's class name by scripting is how you can assure that its style will be changed before calling refreshCSSGradient(). The last example below demonstrates this.

Changelog

1.2 ():
Phong Nguyen raised an excellent point in that stylesheets which don't contain any CSS Gradients should be ignored in order to improve performance (in his case, for example, the jQuery UI stylesheets are large and don't need to be parsed). Now you can add class="no-css-gradients" to any style or link element and that will prevent this script from looking for CSS Gradients to apply with canvas.
1.1 ():
Now if cssGradientsViaCanvas.useCache is set to true, the CSS rules containing gradients are cached in sessionStorage instead of having to be re-parsed out of the stylesheets each time a page loads. For this to work, there must be implementations of JSON.stringify() and JSON.parse() available (e.g. json2.js).
Ability to use data: URIs for images is not explicitly detected since testing for the presence of canvas.toDataURI() is sufficient.
1.0.3 ():
Detecting support for native support for CSS Gradients in Firefox 3.6
1.0.2 ():
Now requiring that gradient(…) only be used with the background-image property instead of with the background shorthand properties since the additional background-* properties are not parsed out.

Examples

The following two boxes are from WebKit's Background Gradients Example (a couple browser-specific background properties are disabled in the first one):

Here is a bonus diagonal rainbow…but wait, there's more! Clicking on the element causes its width to increase and when it does, its gradient is adjusted (see notes above).


Comments

  1. Jeff Schiller

    Interesting. Firefox and Opera support SVG so I wonder if it would have been wise to try and use SVG instead of Canvas? Seems like you wouldn’t have to do a lot of redrawing if that’s the case since the browser would handle stretching for you.

    By the way, I’ve started a ‘gradient picker’ here: http://jgraduate.googlecode.com/ if you care to contribute.

  2. Ajaxian » CSS Gradients for All!

    [...] Ruter has created a very cool library that enables CSS gradients on non-WebKit browsers (at least, a subset). Incredibly cool: CSS Gradients via Canvas provides a [...]

  3. stoimen

    Now that is cool, indeed. Very good job!

  4. James

    Why not use “explorerCanvas” to support IE? (http://code.google.com/p/explorercanvas) It’s basically VML wrapped in a canvas-like API.

  5. Weston Ruter

    @Jeff: SVG cannot currently be used as a background image in Firefox (and neither can a Canvas directly). But Canvas, unlike SVG, can be converted into a PNG via toDataURI(); this regular PNG can of course be used as a background image, as long as the browser supports data: URIs. So this is why Canvas is employed here. Nice benefit here of converting to PNG is that you can save the generated background image to disk and serve it as an external resource for un-supported browsers (read: Internet Explorer).

    @stoimen: Thanks!

    @James: explorerCanvas does not yet implement toDataURI(), so it is not usable. When explorerCanvas implements it, this project can be made to utilize it and bring CSS Gradients to IE8+.

  6. zem

    already using it. working great.

  7. Weston Ruter

    Looks like Firefox 3.6 is going to support CSS gradients natively! https://developer.mozilla.org/en/CSS/Gradients

    It looks like the syntax is a bit different from WebKit’s, however. I plan to update this canvas implementation to also support the new variant Gecko syntax for CSS gradients.

    Now, if only MSIE would upgrade from its filter to this spec to provide the same functionality, especially so that text overlaying the gradients doesn’t lose its anti-aliasing!

  8. Phong Nguyen

    This is pretty nice – except for one major issue I’ve seen. I’m using the jQuery UI library and it includes a fairly large CSS file (nearly 1700 lines!) that causes the forEach(document.styleSheets … ) loop to take a good long while to finish. This blocks the user from doing anything to the page – in my case, for 2-3 seconds. That’s pretty annoying for any user to have to deal with.

    Is there some way to speed up that core loop? (Failing that, I could try and detect if I’m loading certain large files and ignore them).

  9. Weston Ruter

    Excellent point, Phong! I just added the ability (version 1.2) to add the class “no-css-gradients” to prevent the script from examining the file for CSS Gradients. See changelog.

Leave a Comment