Image Tint With CSS

Update: (May 7, 2013) There are now two specifications that make this sort of thing possible with CSS: Filters and Compositing and Blending.

Image Tint With CSSThe other day Paul Irish posted an article that collected together responses to a question that he and Yehuda Katz asked their Twitter folowers.

One of the wishlist items a few people mentioned was “CSS blend modes” with a use case of tinting an image on hover (or tinting it statically, then removing the tint on hover or by some other interaction). My immediate thought was: That should be simple enough, shouldn’t it?

But it’s not so simple. This particular feature request is more along the lines of matching the behaviour of some, or all, the blending modes available in traditional image editors like Photoshop. These might include dissolve, darken, multiply, overlay, and saturation.

In this post, I’ll offer a few solutions for mimicking a CSS image tint or semi-transparent color overlay. Each of these solutions has the drawback of requiring either an extra wrapper element or JavaScript.

Method 1: Pseudo-Element

The list of things you can accomplish with pseudo-elements seems endless and they help with a pure CSS example of an image tint.

Here’s the markup:

<figure class="tint">
  <img src="image.jpg" alt="Example" width="400" height="260">
</figure>

And here’s the CSS:

.tint {
	position: relative;
	float: left;
	cursor: pointer;
}

.tint:before {
	content: "";
	display: block;
	position: absolute;
	top: 0;
	bottom: 0;
	left: 0;
	right: 0;
	background: rgba(0,255,255, 0.5);
	-moz-transition: background .3s linear;
	-webkit-transition: background .3s linear;
	-o-transition: background .3s linear;
	transition: background .3s linear;
}

.tint:hover:before {
	background: none;
}

So what’s happening here? Well, first we have the wrapper element (I’m using the figure element but you could use whatever you want, even list items for multiple images). The <figure> element is positioned relatively to prepare the context for the pseudo-element.

In this example, it’s floated left which will cause it to take the same dimensions as its child image. If your layout requires no float, then you would have to size the wrapper to be equal to the image’s dimensions. Not very scalable, but I can’t think of a better way.

Next we place a pseudo-element inside the wrapper element and position it absolutely. Although it appears “before” the image in the source order, it appears on top of the image due to it being absolutely positioned.

The trick to getting the pseudo-element (which has the tinted background) to fill the whole image is to use a little-known positioning trick. You can actually define the dimensions of an element based on top/bottom/left/right positioning. In this case, I’ve made all values zero, which means they will begin right at the four edges of the element.

Finally, I’ve added a transition and hover state on the pseudo-element. The transition currently only works in Firefox, but that’s expected to improve in the future.

View the JSBin demo below. In this case, the tint appears on each image by default, and if you roll over an image the tint goes away.

Method 2: Pseudo-Element and jQuery

This next method might appease the semantic purists a little more, but it’s essentially the same as the solution above, except this time the tint happens on hover. The CSS is basically the same, but there is no extra wrapper element in the HTML. Instead, I’m adding the wrapper element around each image with jQuery:

$(function() {
	$('img').each(function() {
		$(this).wrap('<div class="tint"></div>');
	});
});

This uses jQuery’s wrap() function to place a wrapper element around all the images on the page. I’m targeting all the images on the page, but if you only want to target a specific image, then you’d just adjust the selector.

In the JSBin demo, below, I’m also adding classes for different colored tints dynamically with jQuery.

Method 3: Opacity Change

Finally, this method is very similar to what I’ve done above. It still requires a wrapper, and you could use jQuery to insert the wrapper, or just put it right in the HTML. Either way, the CSS would look like this:

.tint {
    float: left;
    background: blue;
    margin: 0 20px 20px 0;
}

img:hover {
    opacity: .5;
    cursor: pointer;
}

This time it’s pretty simple. Just give the wrapper element a background color and on hover change the element’s opacity so the color shows through, giving the appearance of an image tint.

What About IE6-8?

IE6 and IE7 don’t support pseudo-elements, so the pseudo-element version won’t work. Also, the IE opacity filters are buggy and from my experience don’t seem to work on hover. Maybe I’m wrong. I didn’t try them.

Although IE8 does support pseudo-elements, I could not get the pseudo-element to appear above the image. The top/bottom/left/right position trick didn’t seem to work, and even if I sized it using width and height, the pseudo-element still didn’t appear above the image. Maybe someone else can try to fix those IE8 problems, but I couldn’t do it. And even the third method wouldn’t work in IE because none of them supports the opacity property.

But, as always, this is mostly decorative, so it does degrade to just a regular untinted image in all versions of IE.

Bonus Method: Pixastic jQuery Library

This solution would be overkill for most cases, but it’s good to know of should you require lots of different image effects that currently aren’t possible with CSS. With the Pixastic jQuery library you can apply all sorts of processing methods on your images including color adjust, blur, sepia (which is kind of what we’re doing here), lighten, crop, desaturate, flip, and more.

Can You Think of Another Way?

So once again, I’m sure I haven’t exhausted all the possibilities here. Can you think of another way to do an image tint effect with CSS?

Update (Oct. 5/2012) This is now possible in supporting browsers using CSS Filter Effects.

Advertise Here

33 Responses

  1. Ana:

    If you have the image as a background image, it can be done with RGBA borders & box-sizing and there is no need for wrappers.
    Like this.
    Of course, some JavaScript will be needed for IE7-8.

    • Very nice! :)

      Edit: One quick note here. While this solution may be fine in some circumstances, it doesn’t really solve the problem I’m trying to address here (which I probably should have made more clear in the article). We want to be able to “tint” or otherwise change an image that actually appears in the source of the page with a regular image tag. Using a background image, the image is not accessible and if it’s mandatory content (and not just decoration) then it breaks the rule of keeping content images within the HTML.

      • Ana:

        I now did it with img tags and I’m thinking about even more effects using gradients for border-image.

        The method that I’ve played with in the past was something like this. I used a div instead of a pseudo-element because I wanted to use transitions.

        • Yes, that’s much better.

          The style changes are really over the top, so not my favourite, but I like that you did it with regular images. I’ll have to examine that a little more when I get some time, because it’s interesting how you’re using a background image on the img element itself.

        • Crazy cool effect!

          On topic, these are all just color overlays. I would use a canvas or svg overlay for a real tint.

  2. WOW used figure attribute on image tagging…
    ty ty ty for tut’s master ;)

  3. There are some interesting things you can do with SVG filters, like http://jsfiddle.net/carey/eqFJf/, but I can only get that working in Firefox.

    Are you sure you can’t have a wrapper element like <a> on each image and use a solution like Ana’s or http://jsfiddle.net/carey/2m8kU/? Hover works better on links in older versions of IE, so my second example works in IE7.

  4. nonames:

    jQuery:

    
    $(document).ready(function(){
    $("div.activer img").css("opacity","0");
    $("div.activer p").css("opacity","0");
    
    $("div.container , div.container2").hover(
    function() {
    $(this).find('div.animer , .animer img , .animer p').stop().animate({"opacity": "0" , "width": "306" , "height": "306"}, "slow");
    $(this).find('div.activer , .activer img , .activer p').stop().animate({"opacity": "1" , "width": "306" , "height": "306" }, "slow");
    },
    function() {
    
    $(this).find('div.animer , .animer img , .animer p').stop().animate({"opacity": "1" , "width": "206" , "height": "206"}, "slow");
    $(this).find('div.activer , .activer img , .activer p').stop().animate({"opacity": "0" , "width": "206" , "height": "206"}, "slow");
    });
     
    });
    

    no css3 required.

    Animates opacity, and scale of img.Was designed to perform a blending between 2 elements, blending in size and opacity.

    More propertys can be added and animated, just add them to the animate function, for starters, start with this:

    
    $(document).ready(function(){
    $("img.hidden").css("opacity","0");
    
    $("img.hidden , img.shown").hover(
    function() {
    $( "img.shown").stop().animate({"opacity": "0"}, "slow");
    $("img.hidden").stop().animate({"opacity": "1" }, "slow");
    },
    function() {
    
    $( "img.shown").stop().animate({"opacity": "1"}, "slow");
    $("img.hidden").stop().animate({"opacity": "0" }, "slow");
    });
     
    });
    

    Where .hidden and .shown are the class tag (img src=”bla.jpg” class=”shown” /) assignated to each image.

    the class hidden will define the image that is initially transparent, and that the hover (placing the mouse over it) will trigger if you place the cursor over 1 or another, it doesnt matter (because of this: $(“img.hidden , img.shown”).hover) while the class shown will vanish to transparent, the .stop elements make the animation flow in and out as you mous over and out the elements, it means that the animation doesn’t have to be completed each time that you hover in and out and it just switches in real time.

    btw, for placing 1 image over theother you canjust use position relative and force one intop of theother, that will give you a blending betweeen img1 and 2.

  5. That is a great and simple solution using RGBA and psuedo elements. I think the simplest solution is always best, and if polyfils don’t work for the older browser support then it degrades fine. Well done!

  6. William:

    Great solution, can you teach me how to display text inside the picture on hover? Thanks!

  7. This is nice, thanks :)

    There are CSS3 filters that are quite similar to this, but not exactly for the same purpose right now. It also you for example to apply sepia/blur/hue-rotate/contrast/etc effects to any element on the page. This is very recent feature but it looks very promising ! Here are some demos if you want to see it in action (latest Chrome build needed for now):
    http://html5-demos.appspot.com/static/css/filters/index.html
    http://girliemac.com/blog/2011/12/21/quick-fun-css3-filter-effects/

    Cheers

  8. Ana:

    I’ve played a bit today with some variations of the third method (opacity change), but using either inset box shadows or background gradients on the containing element.

  9. shedesigns:

    Hi – I really found this helpful – thanks! I’m hoping to use your first method but is there no way for the image to remain a link? It’s kind of frustrating to have a rollover that doesn’t link – or am i missing something?

    • If you need it to be a link, then I would suggest using method #2 or method #3. The first one has the pseudo-element overlaying it, so it won’t work as a link. It’s not supposed to change on hover; that’s just how I did it to make it clear.

  10. artym:

    how i can add external link to tint image?
    link with this doesn’t work:
    HTML:

    
    <a href="anylink" rel="nofollow"></a>
    

    CSS:

    
    .untint1 { position: relative;cursor: pointer;}
    .untint1:before {
        margin: 0 auto;
        content: "";
        text-align:right;
        color:#ffffff;
        display: block;
        position: absolute;
        top: 0;
        bottom: 0;
        left:0;
        right: 0;
        background: rgba(30,30,30, 0.0);
    }
    
    .untint1:hover:before {
        background: rgba(30,30,30, 0.6);
        -moz-transition: all .5s linear;
        -webkit-transition: all .5s linear;
        -ms-transition: all .5s linear;
        -o-transition: all .5s linear;
        transition: all .5s linear;
        content:"anytext";
    }
    

    =((

    • It’s probably because the overlay pseudo-element is blocking the click area.

      I don’t think there’s a way around it. Unless maybe you place another element on top of the tinted image (with absolute positioning and z-index), and then use JavaScript or another link element to trigger the link. But that would be really messy, and probably not worth it. You’re better off doing the image tint in Photoshop and not even bothering with the pseudo-element or other CSS method.

  11. Here’s a good solution to people that want to link the images. I had that problem, so did that: http://jsfiddle.net/robertolux/nSUqV/ :DD

  12. CJ:

    I needed to create a blue duotone effect (similar to sepia except blue).

    After some research and head-scratching I combined the grayscale filter (with SVG desaturate fallback) AND a transparent blue overlay (as described above).

    The result was spot on and more than satisfied both the designer and the client.

    Thanks for this post – it pointed me in the right direction.

    CJ

  13. Sam:

    Nice !

    Could somebody explain me how to do that in WordPress though, I’m having difficulties doing it with WP TwentyTwelve ?

    • Sam:

      Wait ! I did it, I was having trouble with placing the element (it had to be in content.php…!).
      Anyway, great tutorial, thanks !

  14. Adal:

    Great article! For simplicity’s sake I added a slight shadow to my image with the following markup:

    HTML:

    <figure> <img /> </figure> 

    CSS:

    figure { display: inline-block; background-color: black; }
    figure > * { opacity: .95; } 
  15. Whole crap your code worked first time, I nearly passed out from the surprise!

  16. Perla:

    HI, I have a fixed navigation and I did the images tints but when I go down on my page the images with the tint go over my navigation…. do you know why?

    • Yes, it’s probably because of creating a new formatting context. Which one of the above solutions did you use?

      The way to fix it is to use z-index, but I have to know which code you used so I can advise further.

  17. perla:

    Hi Louis,

    Thanks for your help.

    I am doing an assignment, and I want to use the tint on my home page.

    My images have links, but in the moment I use the tint to the images the tint doesn’t respect my links… do you know why ?

    …..
    .tint1 {
    position: relative;
    float: left;
    width:286px;
    height:340px;
    cursor: pointer;
    }
    .tint1:before {
    content: “”;
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background: rgba(117,73,142, 0.5);
    -moz-transition: background .3s linear;
    -webkit-transition: background .3s linear;
    -o-transition: background .3s linear;
    transition: background .3s linear;
    }
    .tint1:hover:before{
    background: none;

    }

    • Yes, it’s because the links have pseudo-element overlays. When you hover over the image, the pseudo-element (e.g. “:before”) blocks the link on the image. On my demo page, I’m using the CSS “cursor” property to make it look like they are links, but they’re not.

      To fix that, you’ll have to use Method 3 in my examples above, or maybe try Ana’s example.

  18. perla:

    Hi Louis,

    Thanks for responding to my questions =), I tried the Method 3, and it is working, but I really like the other way when the tint appear firs because the picture with the tint looks better but I need the link… I will continue to try and see ..
    thanks

    • That’s no problem, you can make it appear first by putting the “opacity: 0.5″ property on the “img” selector, rather than on the “img:hover” selector.

      Also, I noticed this method #3 doesn’t work correctly in Chrome anymore. Interesting.

      EDIT: That was strange, because for some reason I had a black background set on the image elements, which seemed to cause the problem. I’ve removed that and it’s better now in Chrome:

      http://jsbin.com/ruxak/1/edit?html,css,js,output

Leave a Reply

Comment Rules: Please use a real name or alias. Keywords are not allowed in the "name" field. If you use keywords, your comment will be deleted, or your name will be replaced with the alias from your email address. No foul language, please. Thank you for cooperating.

Instructions for code snippets: Wrap inline code in <code> tags; wrap blocks of code in <pre> and <code> tags. When you want your HTML to display on the page in a code snippet inside of <code> tags, make sure you use &lt; and &gt; instead of < and >, otherwise your code will be eaten by pink unicorns.