This started out as a little experiment and eventually turned into quite an endeavor. The task was simple enough; to emulate Internet Explorer’s ‘grayscale‘ filter in all non-IE browsers. The solution, much to my initial surprise, is not as tricky as you would think.
The ‘grayscale‘ filter in IE can be applied to any element and visually transforms the element itself into grayscale. You can apply the filter using one line of messy proprietary CSS:
elem.style.filter = 'progid:DXImageTransform.Microsoft.BasicImage(grayscale=1)'; |
This can also be defined in your StyleSheet:
elem { filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1); /* Element must "hasLayout"! */ zoom: 1; } |
As shown, getting this to work in IE is a piece of cake; other browsers, however, require much more attention!
There are two things to consider; images and everything else. “Everything else” is quite simple; loop through all elements within the document and look for colour properties such as ‘backgroundColor’ and ‘color’, then convert their RGB values to grayscale. There are a few ways of doing this; note that we’re not talking about desaturating a photo; “grayscaling” is slightly different (as I understand it):
// Desaturate: function RGBtoDesat(r,g,b) { var average = (r + g + b) / 3; return { r: average, g: average, b: average }; } // Grayscale: function RGBtoGrayscale(r,g,b) { var mono = parseInt( (0.2125 * r) + (0.7154 * g) + (0.0721 * b), 10 ); return { r: mono, g: mono, b: mono }; } |
So, each element with a colour property has it converted to grayscale; the original colour is stored somewhere for resetting purposes.
Whether our image can be converted to grayscale depends on two things; the browser in question must support the HTML5 canvas element and its ‘getImageData’ method and the image must be hosted on the same domain; externally hosted images cannot be passed into ‘getImageData’ regardless of whether it’s supported. Google Chrome and Safari (<4) don’t support ‘getImageData’ so we’re stuck there, but, on other browsers that support the canvas element “grayscaling” images can be achieved!
The only way to do this is to “manually” traverse all pixels of the image in question and apply the same ‘RGBtoGrayscale’ function as we did for the CSS colour properties. This can really eat up the browser; even speedy JavaScript engines can suffer considerably with large images.
For the reason mentioned above it makes sense to add a ‘prepare’ function to run before anything actually needs to be “grayscaled” – this function can use the classic zero-timeout recursion technique so as not to lock up the browser. If only small images need to be converted then you can avoid using ‘prepare’ and go straight ahead with the brute-force conversion.
Why, oh why?
You may wonder what the point is in “grayscaling” anything… Well, for one; eliminating colour detracts focus from the user therefore leaving their attention open for other focus-grabbing items on your website; e.g. a lightbox. Forum software such as vBulletin makes it so that the page goes entirely grayscale when you click logout; this brings up a confirm box which is quickly and easily identifiable since it’s the one of the only things with colour left on the screen.
The real reason behind this whole “grayscaling” obsession is that I was curious about whether it’d be possible to achieve; I knew about the filter in IE and wondered if other browsers could be made to emulate this handy effect. I know the effect itself might be considered out-of-date but I really don’t care; I was only interested in getting it to work.
Demo
For those blood-thirsty demo hunters lurking around I’ve created a demo page which shows the functionality as described in this post. Remember, it won’t work properly in Safari (<4) or Chrome (and probably some old version (pre v.2) of FF); also remember it’s just an experiment!
The DEMO: /demos/grayscale/
Usage
To “grayscale” an element you need to call grayscale()
with that element passed as a reference, e.g.
var el = document.getElementById( 'myEl' ); grayscale( el ); // Alternatively, pass a DOM collection // (all elements will get "grayscaled") grayscale( document.getElementsByTagName('div') ); // Even works with jQuery collections: grayscale( $('div') ); |
To reset an element (back to its original colourful state) you must call grayscale.reset()
and pass whatever elements you want reset:
grayscale.reset( el ); // reset() also accepts DOM/jQuery collections grayscale.reset( $('div') ); |
The ‘prepare’ function, as discussed earlier, should be called when there’s a large image to process or even if there are several smaller images. Be aware that larger images will take quite a while to process (just a 300×300 PNG takes about 3 seconds in ‘prepare’ mode).
grayscale.prepare( document.getElementById('myEl') ); // Also accepts DOM/jQuery collections grayscale.prepare( $('.gall_img') ); |
Looking for this article in German? Thanks Christopher!
Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!
Nice post. Never used this effect even on IE before.
in chrome 2 it works fine
You Rock James! I’ve been thinking about it for years when i first saw Yahoo completely greyscaled the home page soon after 9/11 attack. Of course that was some sort of Server trick. But Now i can see it’s possible to make non-ie browsers greyscaled. It’s just WOW.
I love researches done 4 curiosity!
But, forgive my ignorance: how do you get the convertion numbers you use to grayscale a color? Why green has more weight than blue?
Wow, I thought I’d have to wait a few years for this! Nice, nice, thank you so much.
Oh, doesn’t work in Safari? Works for me 🙂 (screenshot)
Good work.I will use it in my work.
That’s pretty dope! Thanks for sharing 🙂
This is very cool. Nice work!
your script make firefox use more memory, it increase to 600mo,
i don’t know what’s the probleme
Nice work!
If you want to know why the conversion numbers are used its because your eye is much more sensitive to green than red than blue. Generally accepted values have been calculated by the NTSC (National Television System Committee) as Red: 0.299 Green: 0.587 and Blue: 0.114 but in fact the values will be slightly different depending on the screen you are using. It doesn’t really matter that much.
I’ve also found the canvas tool to be incredibly useful. I’ve used it to generate the mandelbrot set and to create anaglyphs.
Thanks for all the comments! Much appreciated!
@Thomas, thanks for the explanation; I didn’t originally understand the reason behind those different figures, it makes sense now! Also, that’s some really cool work with the anaglyph generator!
@Mafalian, I’m afraid that’s to be expected; processing images using canvas takes quite a lot of memory + processing time.
Son ‘novay…! Damn, colour me impressed. I needed this kind of functionality a couple of months ago and I was looking at some of the bleeding edge CSS3 SVG masks, but the problem is that they only currently work in Opera beta and only work properly on iframes. Annoyingly I saw a demo of Opera doing exactly this to a web page, but it was using Opera’s internal browser rather than an app we can get our hands on.
Anyway, I knew it could be done via the canvas, but you’ve gone ahead and done the hard work of putting down exactly how that’s done.
Bottom line: impressed. Nice one.
I should add, I was going to use the effect on jQuery for Designers, for when the user was watching the video, they could click “lights off” and it would greyscale the entire background (probably layering over a big opacity: 60 div) letting the user focus on the video.
So, for the jQuery rookie guy, this prepares the image in a blog-post, activates grayscale on hover (and removes it afterwards). Check your selectors first.
@The Mafalian – Agreed on that, it leaks like a sinking ship… Safari 4 seems to handle the memory better, but fails when I apply the function on load. But if I use it in $().hover it works.
You should update your post and demo to reflect fixes to the stable channel (as of today, 10/24) in Google Chrome, which specifically addresses the issues seen here (see issue and post)