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.