How To Create Simple, Stylish and Swappable Image Captions

Image Captions

While they say a picture is worth a thousand words, not every image is self-explanatory. Sometimes a few words of description or context can make the difference between a confusing image and a clarifying one. That’s why image captions are often a good idea.

I’ve always liked the idea of giving images captions, but I rarely do. And why not? It seems to me that most image caption solutions out there:

  • Require a lot of excess HTML,
  • Make it difficult to redesign (because of the rigidity, usually),
  • Don’t make the relationship between image and caption clear, or
  • All of the above.

Today, I propose a solution that addresses many — if not all — of these concerns.

You can view the demo here, or read on for the why- and how-to.

How Not To Do Captions

An example of an old-school way to deal with image captions might look like this:

<table class="image-caption">
	<tr>
		<td><img src="myimage.jpg" alt="" /></td>
	</tr>
	<tr>
		<td>This is a caption for the image above.</td>
	</tr>
</table>

One thing this solution does well is make the relationship between the image and the caption clear: items in the same column of a table are (or at least should be) related in some way.

However, it requires a ton of markup for something as simple as a line or two of related text. And let’s pretend you have a blog with 300 posts that contain images with this style of caption. What happens when, a year down the line, you decide the caption would look better above the image in the new design? There goes your weekend.

Here’s another example that’s much cleaner, but has problems of its own:

<img src="myimage.jpg" alt="" />
<p>This image is swiped from Flickr.</p>

This solution is nice and clean, but it makes a lot of assumptions. Your readers should assume, for example, that paragraphs following your images describe that image. Which may be true most of the time, but probably isn’t true all of the time.

And while you could give that paragraph a certain class or ID to style it more like a caption, code-wise they’re still semantically unrelated.

So what’s the solution?

The Goal

My goal was to create something that would:

  • Retain some resemblance to good, semantic code,
  • Keep my HTML as clean as possible, and
  • Be easy to style and restyle at will.

But in order to do any fancy CSS work, I’d need some hooks in my HTML. And adding more HTML makes it less clean, harder to revise in the future, and reduces my odds at keeping things semantically meaningful.

To solve this thorny catch-22, I turned (yet again) to our good friend jQuery.

The Solution

In order to keep my HTML clean, I decided to limit myself to a traditional image tag. Nothing simpler, right?

<img src="my_image.jpg" alt="" class="hascaption" />

I also allowed myself the luxury of a special “hascaption’ class, but if all the images were going to have captions, this wouldn’t even be necessary. But then where does the caption come from? Well, why not the image tag itself?

Image tags have a “title” attribute which is supposed to contain extra information about the image. Extra, related information? That sounds like a caption to me! So let’s use it as such:

<img src="my_image.jpg" alt="" class="hascaption" title="This is going to be my caption!" />

Now our caption is contained within our image tag, which is about as semantically meaningful as you can get. And we’re off to a good start: all modern browsers display image titles as a “tooltip,” which is sort of a poor man’s caption. But we want ours to be fancier, and styleable (you can’t style tooltips).

Here’s some jQuery that provides us a nice structure:

$(document).ready(function() {
	$("img.hascaption").each(function() {
		$(this).wrap('<div class="figure"></div>')
		.after('<p class="caption">'+$(this).attr("title")+'</p>')
		.removeAttr('title');
	});
	$(".figure").width($(this).find('img').width());
});

This code loops through our document and finds all the images with a class of “hascaption” and wraps them in a div called “figure”. Then it adds a paragraph after the image (inside the div) with a class of “caption”. It then fills the paragraph with the contents of the image’s title tag. Then it removes the title attribute to prevent the tooltip from competing with our caption (a bit extreme, but it works).

And the last line of code is a little trick that makes the “figure” div the exact same width as our image, so it only takes up as much room as necessary. If you chose to float your div or specify a predetermined width in your CSS, you wouldn’t need this line.

So now we have a rendered structure that looks more like this:

<div class="figure">
	<img src="my_image.jpg" alt="" class="hascaption" />
	<p class="caption">This is going to be my caption!</p>
</div>

This gives us plenty of hooks for some CSS/JS fun, but it didn’t require us to write any extra code by hand, and keeps our HTML looking clean. Anyone viewing the source will just see the original image tag, not the code above.

And best of all, if a year from now we want the caption to go above the image, it’ll only take changing one line of jQuery, versus changing hundreds of articles manually.

Adding Some Flair

I wanted these captions to have a bit of flair: the caption should appear directly on top of the image when the user mouses over (close visual proximity providing relevance), but disappear when the mouse goes away (so as not to clutter or obscure the image). For that, I needed a couple more lines of jQuery:

$(document).ready(function() {
	$("img.hascaption").each(function() {
		$(this).wrap('<div class="figure"></div>')
		.after('<p class="caption">'+$(this).attr("title")+'</p>')
		.removeAttr('title');
	});
	$(".figure").width($(this).find('img').width());

	$(".figure").mouseenter(function(){
		$(this).find('.caption').slideToggle();
	}).mouseleave(function(){
		$(this).find('.caption').slideToggle();
	});
});

And some quick CSS:

.figure {
	position: relative; }
.figure p.caption {
	display: none;
	position: absolute;
	bottom: 0px;
	left: 0px;
	margin: 0;
	width: 96%;
	padding: 5px 3%;
	background-color: #555;
	color: #fff;
	font-weight: bold; }

Note: The CSS above has been modified slightly from the original to account for an IE7 bug.

The jQuery is making use of the “mouseenter” and “mouseleave” functions built into jQuery. Whenever the mouse enters or leaves the space occupied by the “figure” div, the “slideToggle” function fires.

I think slideToggle is pretty cool: it “slides” the content in if it’s not currently there, and slides the content out if it is already present: it toggles between the two states.

The CSS is doing two primary things: setting a relative position on the figure div, and an absolute position on the caption paragraph. Then we just position the caption at the bottom-left of the figure using the power of absolute-inside-relative positioning (one of my favorite positioning tricks!). I gave the caption a nice dark background and made the text white and bold to provide plenty of contrast, but yours could be styled however you like. I’m also using a percentage width, plus a percentage padding on the left and right sides, to ensure the caption always stretches the entire width of the image.

View the whole thing in action.

Admittedly, this is really just a proof-of-concept and could probably stand to be cleaned up a bit. If there’s any interest, I might post a follow-up article that makes this a little prettier.

22 Comments

  1. WPExplorer said:

    Very cool trick. I will test this one out as soon as I find a good use for it. thanks!

    Ps: thanks for not sending me so much mail. CSS Newbie is one of the few sites that i am subscribed to, that does a great job at sending me only quality articles and only once in a while.

  2. brynmor said:

    Great stuff – very simple and clear to understand.

    One problem: if the caption wording is too short, the overlay is not wide enough to stretch to the full width of the image.

    Any ideas?

  3. brynmor said:

    Solved the problem of short captions :)

    $(“p.caption”).width($(this).find(‘img’).width()-10);

    This goes into the script section straight after $(“.figure”).width($(this).find(‘img’).width());

    The -10 bit is double the value of the padding in .figure p.caption (2 x 5px)

    ***

    You can also add a border to the image via css:
    .figure {position: relative; padding:4px; background:#fff; border:1px solid #999; }

    and change .figure p.caption to have bottom: 4px; and margin-right: 5px;

    Well, it works for me :)

  4. Scott Phillips said:

    I like it! Thanks, Rob.

  5. Dawn Vukson-Van Beek said:

    Ie7 bug- renders the caption on the right, not on the bottom.
    Fix this and I can use this alot! I have hundreds of photos in the old table row method.

  6. Rob Glazebrook said:

    Dawn: Thanks for letting me know about that bug. I fixed the code above to account for the problem — I forgot that you have to specify a “left” absolute placement in IE7 for things like this, or it will behave funny.

    Brynmor: While I was modifying the code, I changed the CSS slightly to always ensure the caption is as wide as the image. I’m using a width and left/right padding percentages that add up to 100%. Your solution works as well, but I try to avoid putting measurements like that in too many places. It’s too easy to change one and forget about the other!

  7. RobShaver said:

    So your solution to CSS not being up to the task of styling your page is to build a JavaScript interpreter to modify your markup dynamically. This seems like a potential maintenance nightmare. Will you add more and more to this interpreter to modify more and more of your markup?

    Don’t get me wrong … I think this is clever … I just wonder if it prudent.

    Peace,

    Rob:-]

  8. Rick Walter said:

    @RobShaver

    CSS is up to the task!! This is from CSS 2.1.

    img:after { content: attr(title); display: block; … other style … }

    Its the browsers that fail. The above works in Opera 10, but not in Firefox 3.5 or 3.6beta4, although Firefox will do generated content for other HTML tags (<a> for example)

    Any generated content completely fails in IE.

    @Rob

    I’m greatly disappointed that you failed to mention the CSS-only solution at all. Your cool javascript solution is IMO a bit too much for general everyday use. It would be great for an image gallery though.

  9. Rob Glazebrook said:

    Thanks for the comments, all. I’m enjoying the conversation.

    @RobShaver: It really depends on your site setup. In my imagined scenario, the alternative is someone dropping dozens of manually-captioned images into something like WordPress, where the user would then have to manually change all the posts (or dive into the MySQL) at a future date. Given the alternative, I’d consider this less of a maintenance problem for what is arguably a change in presentation.

    Given an ideal situation and building from the ground up, I’d probably try to relegate this to a server-side solution like PHP. But I work under the assumption that a large portion of my readers aren’t coding custom CMSes. :)

    @Rick: I’m always sorry to disappoint, but something that fails completely in both Firefox AND all versions of IE is probably not much of a viable solution. I’m willing to admit I probably use jQuery for too much these days, but clinging devoutly to CSS in a situation wherein it fails more often than not (Firefox/IE users make up 80% of my readership) is also a danger!

    Of course, I recognize that I often post articles that include CSS that doesn’t work in every situation under the sun. But in that case, I always prefer it either: 1) Degrade nicely, or 2) Work in the vast majority of cases.

  10. RobShaver said:

    @Rick,

    “The good think about standards is there’s so many of them.” Author Unknown

    Well that’s kind of exactly my point. Why didn’t CSS 1.0 handle this? Was captions on graphics really that hard to anticipate? Will CSS 2.1 be able to make equal hight columns without resorting to unintuitive hacks like huge margins?

    I just think CSS has been brain-dead from the beginning and they’ve been trying to fix it ever since. Until I can reliably create a page that lays out the way I intend it to in every modern browser, I’ll continue to use tables. Oh, I use simple CSS for some of the styling but not so much for the page structure.

    But I’ll keep reading blogs like this looking for the day when CSS make more sense to me.

    Thanks for writing and stimulating the discussion. That’s what I look for in a blog.

    Peace,

    Rob:-]

  11. RobShaver said:

    @Rob

    All my web sites are generated from a database on the server side so I’m always thinking about how to do this better. I do like the idea of including the caption as a title attribute right in the tag if CSS could expand it. As Rick pointed out (and that I didn’t know) is that CSS 2.1 can do this. So in five or ten years when 90% of the browsers being used support CSS 2.1, then I might consider switching to that.

    Keep us thinking!

    Peace,

    Rob:-]

  12. brynmor said:

    This solution is better than many other javascript solutions already available: it does not depend on the width of the image, unlike all the others I’ve looked at.

    The css-only solutions I have seen also have the same drawback. It was this lack of dependency on the width of the image that I was after.

    It works well for a thumbnail gallery, where all images are the same width, although fails for a mixed sized (different widths) set of images on the same page – as does the pure css solution.

    @Rob Great improvement using percentage padding :) I can’t yet see how to add a padded border without the caption extending beyond the image (might just be me). I will have a look at this tomorrow, but went with your earlier script (plus my rough adaptation earlier) to attach the jQuery Lightbox to some thumbnails: http://www.stylecymru.co.uk/captioning/

    @RobShaver You may find that laying out a page is much easier using a simple css grid system that acts like tables. Take a look at The 1Kb Grid (http://www.usabilitypost.com/2009/05/29/the-1kb-css-grid-part-1/) – works well and is quite straightforward with the html marked up in rows and columns. :)

  13. jitendra vyas said:

    You are using P tag for caption. is it good for screen reader? it would be better if u can make this script to use img title=”caption text” in place of P tag

  14. jitendra vyas said:

    I liked this jquery plugin more http://thirdroute.com/projects/captify/

  15. Sean said:

    Just a thought – you don’t really need to assign the img a class. You could identify in your javascript any img that has a title attribute, and then go from there.

    That way, if you chose to remove the caption in the future, you don’t also have to remember to remove the class as well.

  16. MuriImWeb said:

    Nice little snippet,
    thank you.

    @Sean
    Agreed.

  17. brynmor said:

    @Sean Err, doesn’t this cause a problem where the images on the page are all different sizes?

    The width 96% in the css is assigned from the first image that the script encounters on document ready. This width is then applied to all the captions on a page.

    This is fine for a gallery where you make all the thumbnails the same size, but messes up if the images are all different widths: some captions being too short, others too long.

    pause…

    I think this is right, but I’m having trouble demonstrating it! Anyone else able to help out?

  18. brynmor said:

    Demo of different width images here: http://www.stylecymru.co.uk/captioning/index2.html

    There are some weird effects coming up in Chrome…

  19. Walter Earnshaw said:

    Hi_There Rob G n CSS Newbie this is gud stuff
    What is the procedure for me to follow you(both) on Twitter?

  20. Chris Thomas said:

    Why not use the already built-in “alt” tag? That’s what shows up the tooltip when you hover over an image. If javascript is off, then you get the tooltip, but if it’s on, it removes the tooltip and gives you the captions.

    (I’ve never seen “title” before, maybe because I use Frontpage?)

  21. Rob Glazebrook said:

    Hi Chris,

    The “alt” tag isn’t the best choice for this scenario. Technically speaking, the alt tag shouldn’t show up in the tooltip when the user hovers over an image; Internet Explorer breaks this rule, but all other modern browsers won’t show the alt tag as a tooltip. Instead, the tooltip is populated by the “title” tag, which is one of the reasons it is a good choice for what we’re doing.

    And for the record, if an image has both an alt tag and a title tag, Internet Explorer will show the title tag in the tooltip. So feel free to use both!

  22. Chris Thomas said:

    Thank you for explaining the difference :). I think using both would be best. I’ve seen code that uses alt, probably because Frontpage uses it.

14 Responses Elsewhere

  1. CSS Brigit | Create Simple, Stylish and Swappable Image Captions said:

    Create Simple, Stylish and Swappable Image Captions…

    Most image captioning solutions in HTML are messy and tough to swap out later. This CSS/jQuery solution keeps your code clean and upgrades like a dream.

  2. Heti események | I build websites said:

    [...] Shared How To Create Simple, Stylish and Swappable Image Captions. [...]

  3. Inspired: Javascript & jQuery Love said:

    [...] How To Create Simple, Stylish and Swappable Image Captions [...]

  4. Haftalık Derleme – 4 » Müjdat Korkmaz: Blog said:

    [...] Basit ve Şık “Image Caption” Alanı Oluşturmak [...]

  5. Enlaces semanales que no he publicado (42) | Cosas sencillas said:

    [...] How To Create Simple, Stylish and Swappable Image Captions (cssnewbie). Aunque dicen que una imagen vale más que mil palabras, no todas las imágenes se explica por sí mismo. En ocasiones algunas palabras de descripción o el contexto puede ser la diferencia entre una imagen confusa y una aclaración de la misma. Es por eso que la imagen con título son a menudo una buena idea. En este artículo nos propone una solución que aborda el tema de cómo crear una leyenda intercambiable en una imagen, puedes ver una demo del invento. [...]

  6. 45 Powerful CSS/JavaScript-Techniques « Social-Press said:

    [...] How To Create Simple, Stylish and Swappable Image Captions Most image caption solutions require a lot of excessive HTML, make it difficult to redesign or don’t communicate clearly that they belong to an image. Let’s see what we can do to address these problems. [...]

  7. 45 Powerful CSS/JavaScript-Techniques | Tutorial51 said:

    [...] How To Create Simple, Stylish and Swappable Image CaptionsMost image caption solutions require a lot of excessive HTML, make it difficult to redesign or don’t communicate clearly that they belong to an image. Let’s see what we can do to address these problems. [...]

  8. 45 Powerful CSS/JavaScript Techniques « Extreme Design Studio Blog v4.0 said:

    [...] How To Create Simple, Stylish and Swappable Image Captions Most image caption solutions require a lot of excessive HTML, make it difficult to redesign or don’t communicate clearly that they belong to an image. Let’s see what we can do to address these problems. [...]

  9. 45 Powerful CSS/JavaScript-Techniques » Shai Perednik.com said:

    [...] How To Create Simple, Stylish and Swappable Image CaptionsMost image caption solutions require a lot of excessive HTML, make it difficult to redesign or don’t communicate clearly that they belong to an image. Let’s see what we can do to address these problems. [...]

  10. 45 Powerful CSS/JavaScript-Techniques | moreInet.com | Webdesign, Graphic Design Service in Pattaya said:

    [...] How To Create Simple, Stylish and Swappable Image CaptionsMost image caption solutions require a lot of excessive HTML, make it difficult to redesign or don’t communicate clearly that they belong to an image. Let’s see what we can do to address these problems. [...]

  11. Best Photoshop, html, javascript and php tutorials » Create Simple, Stylish & Swappable Image Captions said:

    [...] Click here for this Tutorial! Previously Posted Tutorial: Sexy Overlaps In CSS [...]

Leave a Comment