Blog

2014.03.22

Cross-Browser Grayscale image example using CSS3 + JS v2.0. With browser feature detection using Modernizr

A couple of months ago I did a tutorial that showed how to create grayscale images using CSS and JS on all major browsers, including Internet Explorer 10 and 11 that no longer support CSS filters. Since that example was using browser detection and was based on user agents, it was not very reliable and not exactly the best way to create it. Besides $.browser has been deprecated. So I made an update of the previous grayscale image tutorial and this one is based on browser feature detection + uses JS Modernizr library.

View the demo or Download source

Cross-Browser Grayscale image solution

At first we have to include jQuery library and Modernizr since both of them will be used for browser feature detection

<script type='text/javascript' src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type='text/javascript' src='js/modernizr.custom.js'></script>

Grayscale images and Internet explorer 10, 11 using browser feature detection

At first, let us cope with Internet Explorer 10 and 11 (the previous versions had native CSS filter support, so they do not have issues – at least the ones with native grayscale filters). This time we will use browser feature detection instead of user agent detection which was kind of the bad practice.

So at first we have to find out the differences in CSS features between IE10 and IE11 versions. There are many CSS properties that are supported since Internet Explorer 10 e.g. msTouchAction, but the only CSS property that is supported by Internet Explorer 11 is msTextCombineHorizontal. So this will be the way that we can tell if the browser is IE10 or IE11. Actually the grayscale script will be the same for both Internet Explorer browsers, but I left it in case if somebody would need such a detection and here I must thank Tim Pietrusky for the finding

// IE 10 only CSS properties
var ie10Styles = [
'msTouchAction',
'msWrapFlow'];

var ie11Styles = [
'msTextCombineHorizontal'];

/*
* Test all IE only CSS properties
*/

var d = document;
var b = d.body;
var s = b.style;
var brwoser = null;
var property;

// Tests IE10 properties
for (var i = 0; i < ie10Styles.length; i++) {
	property = ie10Styles[i];
	if (s[property] != undefined) {
		brwoser = "ie10";
	}
}

// Tests IE11 properties
for (var i = 0; i < ie11Styles.length; i++) {
	property = ie11Styles[i];
	if (s[property] != undefined) {
		brwoser = "ie11";
	}
}

So after we have saved in the variable our IE verion, we can fire grayscale image function that uses canvas and turns the images black & white. Here we have to take into consideration new Microsoft Edge browser and have to add extra check to see if the browser supports css filters. If it does, then it is Microsoft Edge, since neither IE10 nor IE11 support them. We also add special class to the body in order to target with CSS exactly Microsoft Edge browser with the grayscale filter

//Grayscale images only on browsers IE10+ since they removed support for CSS grayscale filter
	 if(brwoser == "ie10" || brwoser == "ie11" ){

		//If the browser supports Filters, then we assume that it is Microsoft Edge
		if (Modernizr.cssfilters){
			$('body').addClass('edge'); // Adds Microsoft Edge class to the body
		} else {
			$('body').addClass('ie11'); // Fixes marbin issue on IE10 and IE11 after canvas function on images
			$('.grayscale img').each(function(){
				var el = $(this);
				el.css({"position":"absolute"}).wrap("<div class='img_wrapper' style='display: inline-block'>").clone().addClass('img_grayscale ieImage').css({"position":"absolute","z-index":"5","opacity":"0"}).insertBefore(el).queue(function(){
					var el = $(this);
					el.parent().css({"width":this.width,"height":this.height});
					el.dequeue();
				});
				this.src = grayscaleIe(this.src);
			});

			// Quick animation on IE10+ 
			$('.grayscale img').hover(
				function () {
					$(this).parent().find('img:first').stop().animate({opacity:1}, 200);
				}, 
				function () {
					$('.img_grayscale').stop().animate({opacity:0}, 200);
				}
			);

			// Custom grayscale function for IE10 and IE11
			function grayscaleIe(src){
				var canvas = document.createElement('canvas');
				var ctx = canvas.getContext('2d');
				var imgObj = new Image();
				imgObj.src = src;
				canvas.width = imgObj.width;
				canvas.height = imgObj.height; 
				ctx.drawImage(imgObj, 0, 0); 
				var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
				for(var y = 0; y < imgPixels.height; y++){
					for(var x = 0; x < imgPixels.width; x++){
						var i = (y * 4) * imgPixels.width + x * 4;
						var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3;
						imgPixels.data[i] = avg; 
						imgPixels.data[i + 1] = avg; 
						imgPixels.data[i + 2] = avg;
					}
				}
				ctx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);
				return canvas.toDataURL();
			};
		};
	 };

Grayscale images on Opera, Safari and Firefox using browser feature detection

In order to test these browsers, I used Modernizr which has a library of CSS filters. So the idea is to check if the browser supports filters. If it doesn’t, then we fire the grayscale.js solution which has been kindly provided by James Padolsey

// If the browser does not support CSS filters filters, we are applying grayscale.js function
// This part of Grayscale images applies on Opera, Firefox and Safari browsers
if (!Modernizr.cssfilters) {
	var $images = $(".grayscale img"), imageCount = $images.length, counter = 0;

	// One instead of on, because it need only fire once per image
	$images.one("load",function(){
		// increment counter every time an image finishes loading
		counter++;
		if (counter == imageCount) {
			// do stuff when all have loaded
			grayscale($('.grayscale img'));
			$(".grayscale img").hover(
				function () {
					grayscale.reset($(this));
				}, 
				function () {
					grayscale($(this));
				}
			);
		}
	}).each(function () {
	if (this.complete) {
		// manually trigger load event in
		// event of a cache pull
			$(this).trigger("load");
		}
	});
}

Tested and should work on:

  • Firefox 3.5+
  • Chrome, Safari 4+
  • Opera 9+
  • Internet Explorer 7, 8, 9, 10 and 11 (IE11)
  • Microsoft Edge

View the demo or Download source

In categories: Tutorials, Website development

Nauris Kolāts

Nauris is a freelance designer / developer who loves to dig into the UX as much as in the ground for fishing worms. And fishing is just one amongst the long list of his active lifestyle hobbies.

Other posts

Your thoughts

Join the discussion

  1. chandru

    Excellent. Thanks for your post

  2. maureen

    Has anybody had a problems with solution and picturefill?
    It seems to work if I just have an image, no data -src. But I need the picturefill because the client is an artist so things HAVE to look nice. thinkvolution1.com/rickpas

    • Hey!
      This solution won’t work on data-src.
      I haven’t come across such a solution yet.

  3. Alex Wright

    I’m getting this error in IE 11:

    “SecurityError. grayscale-functions, line 49”

    Line 49 is:
    var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height);

    Not really sure why this would be considered a Security Error. Any ideas?

    • Hi, Alex!

      I can’t really tell why this line would be causing some kind of a security error, but I don’t think that this is a problem that affects user experience :)

  4. Ivan

    In latest Firefox (29.0.1) it does not work if the html file is not in the root directory. Test it in the following way:
    1. Download source
    2. Unzip the archive. Create a new directory and cut&paste there the index.html.
    3. Change paths to JS, CSS and IMAGE files in index.html accordingly.

    …and it does not work. Any ideas how to fix it?

  5. Aleksey

    >> I’m getting this error in IE 11:
    >> “SecurityError. grayscale-functions, line 49″

    This happens because of canvas’ security restriction because of cross-origin requests for pictures. Here, the example is probably loaded on localhost using the file://protocol, which always fires this (at least, in IE). The solution is to fire the example at some web server – any one will do, including built-in servers of PHP, Django, etc.

  6. Su

    Hi, really like your solution!
    How do I go about to have images coloured on start and greyed out on mouseover?
    I couln’t really figure out

  7. Kirill

    Works perfectly on DrupaL, thanks a lot man
    http://www.aloud.es/en/blog

  8. CD7

    Any quick pointer how I could make the images fade back to color rather than be color straight away after hover?

    • Hola, Esko ;)

      I know that on the latest Internet Explorer versions there is a Fad-In transition from grayscale to color.
      On other browsers I haven’t tried to do it.

  9. […] Update: This tutorial uses browser user agent detection which is fully functional but has been deprecated for a while so I have created another grayscale image tutorial that uses browser feature detection. […]

  10. Daniel Brown

    This is amazingly helpful. Thank you so much for such an awesome solution and thorough walkthrough.

  11. mare

    This solution looked promising but after further inspection proved to not be working with responsive layouts especially in IE 10+.

    I changed the IE10/11 code in functions.js to this to handle Bootstrap’s grid better and put position:relative on the wrapper rather than having it on the outer elements (because with grid you have your images inside columns which often have padding on both sides):

    //Grayscale images only on browsers IE10+ since they removed support for CSS grayscale filter
    if(brwoser == “ie10” || brwoser == “ie11” ){
    $(‘body’).addClass(‘ie11’); // Fixes marbin issue on IE10 and IE11 after canvas function on images
    $(‘.grayscale img’).each(function(){
    var el = $(this);
    var correctWidth = el.width();
    var correctHeight = el.height();
    el.css({“position”:”absolute”}).wrap(“”).clone().addClass(‘img_grayscale ieImage’).css({“position”:”absolute”,”z-index”:”5″,”opacity”:”0″}).insertBefore(el).queue(function(){
    var el = $(this);
    el.parent().css({ “width”: correctWidth, “height”: correctHeight });
    el.dequeue();
    });
    this.src = grayscaleIe(this.src);
    });

    However, even with these changes the solution doesn’t work fine on responsive layouts in IE – after trying to resize the browser all hell breaks loose. Unfortunately it is impossible to use this in production.

  12. Yannis

    Not working for Firefox 35.0 (Windows7)

  13. Andrés Soto

    It works!! thanks a lot!..
    Only i have one problem.. i need the oposite effect.. the images is in color and grayscale in hover.. how can i do that? im trying to edit de js but dont work

  14. Peter

    Does not work in FF(I used 38).
    So I added back this line in grayscale.css:
    filter: url(‘../js/filters.svg#grayscale’); /* Firefox 3.5+ */
    and js/filters.svg file from Your first version and it works fine.

    Would be cool if there where built in function for IE10 & IE11 on window resize event that dynamically changes all .img_wrapper blocks width and height for responsive images ;)

  15. Ellen

    Hmm . . . doesn’t seem to work in my Ruby app. Which is less surprising when I see that the demo doesn’t work in my Firefox browser, either! It would help if the tutorial would suggest possible file names . . . does all of this code go in one .js file? Oh, and I’ve downloaded the source code and can’t open the .js files in there. Don’t know why. Is anyone else having these problems?

    • Hello, Ellen!

      The problem with the latest Firefox is fixed now.
      I would suggest to download the example once again :)

  16. Sameer Ahire

    Hi,
    This is not allowing me to change the images or upload new image. Once Image-A is grayed, and I want to overwrite Image-A with Image-B. it does not allow me to do so.

    • Sounds like you are trying to do that on the fly, like using Ajax. I guess then you would have to remove the image A, replace it with image B and after that is done – fire the Grayscale function on it once again.

  17. Sameer

    Hi Nauris,

    I am trying to upload images using “jquery.uploadify-3.1.min.js”. While doing a browse and upload for images, the script throws an exception at line

    var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height);

    with canvas.width & canvas.height going 0 (zero).

    Secondly, there is problem when I try to upload images with more than 4 MB size as it takes time to generate the URL.

    Your script is really helpful, but I am facing these few issues. Can you plz help in resolving them.

    • Hi, Sameer, it might be that you have run into trouble with some other JS plugins that you are using on your project. Or the other possible reason is that you are trying to make images grayscale on the fly – you might want to try to setup and fire Grayscale functions on the image strictly after it has been uploaded, if you will try to do that simultaneously, you might run into such problems, meaning that JS script does not receive actual data about image height and width.

      And I wouldn’t suggest you to upload 4MB image files, that is way too much than we usually need since the page load times will be depressing.
      Hope that this helps.

  18. Channabasappa

    Hi Sir,
    First of all i want to tell great thanks for sharing this one,its helped me a lot.
    How to do this one on click of image instead of hover..i have requiremet on onclick of image..plz help me

    • Hello,

      To do this, you will need some knowledge working with JavaScript.
      Basically what you will need to do is to open functions.js file and replace this

      $('img').hover(function () {

      with this

      $('img').click(function () {
  19. betty

    Thanks for this code. It would be a nice improvement if it could be more flexible. I need to make the opposite effect (grayscale on hover), and the website link you gave for your previous project where you used this it not working (http://www.lipsandkiss.com/shop/categories).
    Thanks in advance!

    • Hi, yes, the owner of that website has taken it down for some time so the example is not there anymore.
      And it doesn’t sound too hard to achieve the opposite effect, start by swapping CSS hover definitions with regular definitions of the element.

  20. Hello guys, I have just added support for Microsoft Edge browsers. Since Microsoft Edge supports CSS filters don’t have to use JS or svg grayscale solutions. And by the way, please note that currently Microsoft Edge comes with disabled CSS filters, you have to enable them at first to use them.

  21. rafee

    Hi if I add different images it is not working in ie 10 and 11…Please clarify??

    • Hi Rafee,

      The grayscale solution works on all images, no matter the size you put in them.
      Maybe you have misspelled some classes and therefore the grayscale functionality is not fired on them?

  22. Dan Hall

    Thank you for offering this solution! This is great. In my setup, div.grayscale contains an image and a couple of other divs with text. I would like the image to fade to color on hover over any part of div.grayscale and not just when I hover over the image itself. If you don’t mind offering some guidance, how would I go about modifying your code to do that?

    • Hi Dan,

      Could you please create a fiddle or give me a link to your code example so that I can see that in action and help you with it?

  23. Dan Hall

    Thank you so much for the reply, Nauris. I’m using this on a site that is still in development here. http://innovativetraumacare.com/neW_$ite/ See the 3 Case Scenarios below the large image slider. You will see that the title and learn more button do their hover effects when the containing a.article-link is hovered over. Your effect comes into play when you hover over the image itself, but I would like your effect to occur when a.article-link is hovered over. Thank you for being willing to take a look at this. I appreciate your time and thoughts!

    • Hi Dan!

      You could start by opening and changing grayscale.css these lines:

      .grayscale img:hover {
      	filter: none; /* Applies to FF + IE */
      	-webkit-filter: grayscale(0);
      }
      .edge .grayscale img:hover{   /* Applies to Microsoft Edge */
      	-webkit-filter: grayscale(0%);
      }

      Into this according to your code:

      .grayscale .article-link:hover img{
      	filter: none; /* Applies to FF + IE */
      	-webkit-filter: grayscale(0);
      }
      .edge .article-link:hover img{   /* Applies to Microsoft Edge */
      	-webkit-filter: grayscale(0%);
      }
  24. Jonathan

    Thank you for sharing this, but unfortunately it’s not working on IE11 with jQuery 1.9+ (I’m working with jQuery 1.11 and can’t downgrade)… any help?

  25. Jonathan

    Yes, I tried with jQuery-migrate, but it’s still not working… :-(

  26. Jonathan

    It’s working now. The problem was in my JS stack order. :-)

  27. Dan Hall

    Just following up to see if you have had a chance to look at my question.

  28. Sweta

    Hi, Thanks a lot for such a detailed post. It is very helpful for me.

    I had one question. The code works fine if the images are stored locally.

    I need to get it work if I am using it for images from external source. The code works perfectly in other browsers except IE11. Any thoughts or helpful information please do share.

    Thanks and Regards,
    Sweta
    Technical Team Lead.

    • Hello Sweta,

      If you are on Microsoft Edge, you will have to first enable CSS Filters which by default are disabled.
      Enter in the address bar this about:flags

  29. Dan Hall

    Thanks so much, Nauris! Your edit suggestion on 14-01-2016 did the trick.

  30. Katasun

    How to exclude some images like logos or image-links from your function. I am using the wordpress plugin and it works fine. –> But I want the page logo to stay as it is . maybe some one can give me a hint.

    • Hello Katasun,

      You can use CSS and create a class for your logo and after that add this to your .css file

      .yourLogoClass{
          filter: none; /* Applies to FF + IE */
          -webkit-filter: grayscale(0);
      }
      
  31. Katasun

    Thank you very much for this script.

    It works fine with the most browsers. I have problem with an older IE Version on Windows 7.

    But in the moment I don’t find a good solution to exclude one or two images. I have a site where I use your script and it works fine for all images. But one of the images is the site logo. And I like to exclude the logo from this function.

    Does anyone have a idea how to solve this or just a direction to point me to a solution?

  32. Katasun

    Sorry I asked you twice, and thank for the solution.

  33. Nora

    Great plugin, thanks a lot. I just tested it on Safari 10 and it seems to have problems there. The other version (http://www.majas-lapu-izstrade.lv/cross-browser-grayscale-ie11/) works well there. Is there already a solution for this or would it be better to switch to the other version?

    • Thanks, please use the one version that fits your project best. Since both of these projects are quite old and currently I do not have any time on my hands – I won’t be updating them anytime soon :)

  34. wang

    ie 10-11 is not support。。

    • Just tested it in IE 10 and 11 – Nothing has changed, the demo still works beautifully on these two browsers.

  35. 何忠峰

    Hi,IE 10-11 is not support

    • Hi and thank you for bringing this to our attention.
      This demo still works for IE 10 and 11, there was a slight issue with loading our page in IE 10 and 11 therefore you had issue reaching to it, but we have fixed it now.

Latest work