CSS Gradients via Canvas
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.
Download and fork the code on GitHub: http://github.com/westonruter/css-gradients-via-canvas
Changelog
- Updated license to be GPL/MIT dual license instead of just GPL.
- 1.3 ():
- Detecting native support in Firefox 3.6; it had only been detecting support for 3.6 alpha, which had significantly different syntax. I ported the linear gradient examples over to use the new native Firefox syntax, but am still working on the radial gradients; the syntax has changed a lot!
- 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 anystyleorlinkelement and that will prevent this script from looking for CSS Gradients to apply with canvas. - 1.1 ():
- Now if
cssGradientsViaCanvas.useCacheis set totrue, the CSS rules containing gradients are cached insessionStorageinstead of having to be re-parsed out of the stylesheets each time a page loads. For this to work, there must be implementations ofJSON.stringify()andJSON.parse()available (e.g. json2.js). - Ability to use
data:URIs for images is not explicitly detected since testing for the presence ofcanvas.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 thebackground-imageproperty instead of with thebackgroundshorthand properties since the additionalbackground-*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):
Note the following WebKit radial gradient example is very dificult to reproduce using Firefox's syntax. Special thanks to Mark McLaren for writing a close fascimile which appears below:
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). (Clicking will not have the desired effect because, again, it appears your browser is not supported.)



Comments
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.
[...] 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 [...]
Now that is cool, indeed. Very good job!
Why not use “explorerCanvas” to support IE? (http://code.google.com/p/explorercanvas) It’s basically VML wrapped in a canvas-like API.
@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 supportsdata: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+.already using it. working great.
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!
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).
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.
I suggest you add support for MooTools as well. Probably just have to check for window.MooTools and window.$$ like the Prototype check
[...] examine some of the differences in syntax when working with the -moz and -webkit vendor prefixes.(Kind of) Cross-Browser CSS Gradients via CanvasCSS Gradients via Canvas provides a subset of WebKit’s CSS Gradients proposal for browsers [...]