Grow your CSS skills. Land your dream job.

A Journey with Vignetting (That Doesn’t Get Very Far)

Published by Chris Coyier

Ol' Trent posted a quick tip post on using inset box-shadow for some simple yet classy effects. One of those techniques ended up with an vignette effect over top of an image. Like this:

Yeah, you could do it in Photoshop, but doing it through CSS means 1) it's non-destructive 2) can be altered programmatically 3) is super consistent

When I first saw the example, I was like, hey that's pretty cool, but why the wrapping div? Browsers allow box-shadow on <img> elements, so why not just use an inset box-shadow on it. That would be cleaner HTML.

<img src="amazinglandscape.jpg" alt="ooooh ahhhh" class="vignette">
.vignette {
	-webkit-box-shadow: inset 0px 0px 85px rgba(0,0,0,0.4);
	-moz-box-shadow:    inset 0px 0px 85px rgba(0,0,0,0.4);
	box-shadow:         inset 0px 0px 85px rgba(0,0,0,0.4);
}

Turns out, you can use inset box-shadows on images, it's just the browser sets it behind the image, which mean if you use a fully opaque image (very likely if you are interested in vignetting it), you won't see the shadow at all. To which Trent says:

While this seems pretty useless, it does make sense when you consider other kinds of content. For example, if you inset a shadow you probably wouldn’t want it overshadowing the text within.

I also agree with another point Trent makes: perhaps the official spec for box-shadow should be changed to say that when applied directly to an image the shadow should be on top. Alas, the spec nor the implementations do it this way.

Then I thought, hey wait a minute, let's not resort to an wrapping element just yet. After all, keeping our HTML free of non-semantic wrappers is a noble goal. Why don't we use a pseudo element on the image, which we can force to sit on top and be the exact size of the image, and put the inset box-shadow on that?

.vignette:after {
	-webkit-box-shadow: inset 0px 0px 85px rgba(0,0,0,0.4);
	-moz-box-shadow:    inset 0px 0px 85px rgba(0,0,0,0.4);
	box-shadow:         inset 0px 0px 85px rgba(0,0,0,0.4);

	position: absolute;
	top: 0; left: 0; bottom: 0; right: 0;
	content: "";
}

As I was working on a demo for this, it kept not working and confusing the heck out of me. For a minute I thought, I wonder if :before and :after pseudo elements just don't work on inline elements*, but I proved they worked with spans and so that was out.

Then I heard from Nicholas Gallagher:

No elements with an 'empty' Content Model support generated-content via pseudo-elements

By which Nicolas essentially means any element that doesn't have innerHTML. Think:

<br />
<br />
<input />
<img />

This is apparently because the spec didn't explain how to deal with elements of this type.

Much like the inset box-shadow thing, it kind of makes sense. You can think of a :before pseudo element as being within the tags but before other content, and an :after pseudo element as being with the tags but after other content.

<div>
   <!-- :before pseudo element -->
   Hi! I'm content!
   <!-- :after pseudo element -->
</div>

<img /> <!-- Where would pseudo content go? -->

For jQuery people, Tim Wright explains:

it's that :after is more like jquery's .append() than .after() so you can't blop something in the middle of <img>

If you are thinking, dude, just put the pseudo elements AFTER or BEFORE the darn element, you aren't alone. The problem is that's not consistent with how it works with other elements and the spec doesn't cover it. To make things a bit more complicated, that's actually what Opera does.

So anyway... that's it for ideas on how to do it cleanly. Let's just use the wrapping div and get it over with:

<div class="vignette">
	<img src="handsomepeople.jpg" alt="ooooh ahhhh">
</div>
.vignette {
	-webkit-box-shadow: inset 0px 0px 85px rgba(0,0,0,0.4);
	-moz-box-shadow:    inset 0px 0px 85px rgba(0,0,0,0.4);
	box-shadow:         inset 0px 0px 85px rgba(0,0,0,0.4);
	
	line-height: 0;         /* ensure no space between bottom */
		
	display: inline-block;  /* don't go wider than image */
}
.vignette img {
	position: relative; 
	z-index: -1;            /* position beneath */
}

One possibly-desirable-possibly-annoying side effect? With the div overlay you can't right-click-save the image or drag it to your desktop. Super primitive image protection.

View Demo

Related

  • Another technique by Dudley Storey which has you putting the image as a background-image of the vignetted element. Probably better for one-off's but harder to use for sites where you just want to be able to use it wherever you want without needing to change CSS all the time.

* By the way, I was reading Introducing HTML5, and learned an interesting point. In HTML5, there really is no distinction between an "inline" element and an "block" element. All elements are inline until specified otherwise by a browser or user stylesheet.

Comments

  1. Permalink to comment#

    Thanks for this article. I had no idea you could specify “inset” for box shadows! Can’t wait to try this out.

  2. Permalink to comment#

    It is cool that you demonstrate how to do this and it certainly is a clever technique. Conceptually I’m not a big fan of using CSS to actually “alter” images. It is different when you apply a normal shadow or a border around the image, but actually styling the image content does not sound right to me. I don’t think image content needs styling. If it needs it, it should be part of the image itself.

    This is of course just an opinion and I can see how you could make cool hover effects and such with this technique.

  3. Permalink to comment#

    Quick word of warning – it seems that higher blur values result in bit of a speed reduction.

    On your demo page, simply scrolling up and down with my scroll wheel causes some noticeable strain on the browser. When I turn of the blurs within the web inspector – things are once again smooth.

    It’s a nice effect and easy enough to style – but use with caution.

    • Permalink to comment#

      Yeah, and it really makes things sluggish if you have multiple box shadows like this.

      I tried the vignette effect on the background of a content column once (with something like 45px blur) and it chugged so much I had to take it off. Hopefully we’ll see performance improvements in the future, it can really look great.

  4. Permalink to comment#

    I love me some inset box shadows. I find myself designing in CSS more than in Photoshop these days.

  5. Permalink to comment#

    3rd option (and best as of 2010 IMO): create a tranparent png for the vignette and overlay with abosolute positioning. Works in all browsers now, is non-destructive/programmatic, gives you more artistic control and can be used for rollovers.

    • Permalink to comment#

      however, it also:
      …creates an additional http request,
      …is more bandwidth,
      …is extra markup (which, unlike the wrapper div, isn’t as easy to modify programmaticly – the wrapper can be effectively disregarded by simply commenting out the css rule)

      most importantly, the css method gives a more consistent result: the blur will always be 85px (or whatever). With image overlays, if you have multiple image sizes, you’ll either need to make multiple overlay images or deal with some of the shadows being larger (and different resolution) than others.

    • I do not tottaly agree with you on this one. Just put the .PNG overlay in a sprite and the bigger bandwith is nearly gone. And so is the additional http request. And as far as the extra markup go. I don’t see it as a big problem. You could do something fancy with jQuery to do the markup so it’s invisible in your markup when you work on the file.

      As far as different sizes go. I agree with you. It’s way harder to do it with .PNG then doing it with CSS. But a more consistent result? I don’t know because the Box Shadow rendering is different in browsers. And even OSs. And I would probably use the vignette for thumbs which usually have the same size. So I would also go with .PNG at this moment in time.

    • j
      Permalink to comment#

      You’re right, but I never said I was a purist. Since CSS3 won’t be supported in 50% of the world’s browsers for years… my version with the one extra line of code will. Sure it would be nice if CSS3 worked the way it should, but thanks to you know who it doesn’t.

    • Permalink to comment#

      Rick – good points. The main advantage, imo, is the consistency. And though it may not be consistent across browsers, it will be consistent across every image on the page – and easier to accomplish.

      j – absolutely.

  6. Permalink to comment#

    Probably a good idea to create the wrapper element on the fly with JS, to keep the markup clean. Just use the class to find the image. It’s not essential content anyhow, so using JS to produce it would not harm accessibility.

  7. Foodstuff
    Permalink to comment#

    I was thinking of a bit of jQuery along the lines of;

    $(document).ready(function(){
    $('img.vignette').wrap('<span class="realVignette" />');
    });

    …would be an easier to manage solution.

  8. Jeethu
    Permalink to comment#

    Instead of a div tag, a figure tag with a class “vignette” would be more HTML5-ish, being more of a semantic markup and all.. Great piece btw..

  9. Permalink to comment#

    Agreed with the Figure tag. That’s what I’d thought of after your third paragraph, but only because I’d been recently working with the figure tags.
    Excellent writeup, discussion of approaches, and final idea.

  10. It seems that pseudo elements work with things like input tags… that’s a bummer though.

    Would it be possible to do the image with an opening and closing tag? seems like that could work out? Honestly, I’m not sure if that’s valid though.

  11. Andy Ainley
    Permalink to comment#

    Very clever but I still feel Photoshop is the way to go. Using is Photoshop only destructive if you can’t use layers. It’s also not more consistant than using photoshop – multiple browsers, only 1 photoshop. The only real advantage you state is that you can do it programmatically.

    I think where this could come into it’s own would be retro-fitting an old site. Interesting article

  12. Permalink to comment#

    In relation to your last point about inline elements in HTML5 – does that mean that you can wrap any element in any other element? E.g. you can’t usually wrap an <a> round a <div>, but is that legal in HTML5?

  13. litek
    Permalink to comment#

    This can be done with only element (so far had time to test it only in FF I can see no reason why it shouldn’t work in different browsers). Just set the image to background and apply the inset shadow. Clearer markup, but requires you to specify the dimensions.

    When using two elements, you my create even better vignette with radial gradient.

  14. ali
    Permalink to comment#

    Thanks.
    by this Technic you can not save images on the page.

  15. Really nice. This gets rid of the “oh, I forget to inner shadow that image..” Also a cool hover effect .vignette:hover {…}

  16. Permalink to comment#

    It’s important to note that Chrome on Windows has an issue when you combine an inset box-shadow and rounded borders. Example here: http://darcrobotics.org/?page=11

    The bug has been submitted, you can view the (lack of) progress in the resolution of this issue here: http://jordandobson.com/_expirements/css/vignette/

  17. NR
    Permalink to comment#

    Simple image protection? In Safari on Mac, you can easily still drag-and-drop the image to your desktop.

  18. Permalink to comment#

    I fail to see why this is classy, as I remember we were doing those insets in PS7 as teenagers and nobody really liked it. Perhaps inset is more useful in some other situations? :)

    Digging the new footer a lot, by the way…

  19. Long ago I tried using top: 0; bottom: 0; & a width as an alternative to background-image based Faux Columns. It seemed to work great but lots of people told me I shouldn’t expect it to work in the future.

    What do you think?

    You’re basically using the same technique with your top: 0; left: 0; bottom: 0; right: 0;

    • I use it all the time and it makes complete sense to me. I’m not sure why anyone would think that would be removed. It does work great and it’s something you should use!

  20. What I’d like more than just box-shadows over content is an actual Foreground Image!

    I had a project once where they wanted a decorative element in the bottom corner of every user profile photo. Not a legal watermark, just a decorative thing. I could have used a foreground image for that instead of having to add extra divs to the template.

    It might also be useful to be able to use an inset box shadow as a mask.

    What’s the status with the masking stuff apple was working on for safari?

    • That’s where you want to use ::before and ::after for those type of “foreground images”. It would be similar to the approach I used to overlay the CSS3 gradient image.

      Unfortunately you couldn’t use inset box shadow as a mask because it’s not considered an “image”… and you need to use an image with a mask.

      The masking in Safari works great. And from the demos I’ve seen from Firefox for CSS + SVG for masks seems to work well too. Here’s an example of using a masks to recreate apple’s iOS tabbar: http://cl.ly/2Hyj – Make sure you check it out in webkit.

  21. Permalink to comment#

    the performance is really bad in Safari!
    I don’t know why but all shadow manipulation via CSS perform poorly on my Safari /iMac 2.3GHz-CoreDuo
    Is there something wrong with my installed version of Safari or do others discover the same issue?

    • Nope… the performance is pretty bad with box-shadows… somwhere around 20-30px of blur really starts to slow Safari down. That’s why I one of my attempts was to use a CSS3 gradient.

  22. The problem with this is though that its not W3C compliant, and as for worrying about photoshop being destructive, just make sure that you save the original, its not rocket science guys!

    • j
      Permalink to comment#

      I can see the point of moving away from Photoshop and destructive where possible. Have you ever had a client come to you asking for changes, but couldn’t provide the source files because the old designer held on to them? Also this is 2010 and the era of social networking. Lots of images are being submitted by users with no tech skills to add a vignette or drop shadow. Then there’s being able to work faster. HTML5/CSS3 won’t replace photoshop any time soon, but because it’s so easy with a few lines of code to spiff up an otherwise plain design, already we are seeing the web taking a step forward in terms of design…

  23. Permalink to comment#

    Great explanation. Now I know, why :before or :after have problems with or any such empty elements.

    Pretty much enjoyed “Super primitive image protection” too :)

  24. Thank you for sharing! I really enjoy your posts and lynda.com classes. I feel like I can relate to you which makes it easier to learn the material.

  25. Great post! I had no idea you could do this. I hope to use this in my next web gallery site. Thanks Chris!

Leave a Comment

Current day month ye@r *

*May or may not contain any actual "CSS" or "Tricks".