Tuesday, February 10, 2009

How to vertically center images in a div

I really like this layout for displaying a set of images.
 
Trouble is, I'd always found it impossible to do without using Javascript or really nasty tricks.

Now of course if you know your image dimensions it's easy, but I never liked the idea of storing that info in the database just to make this layout possible. I also don't like the idea of reading the dimensions in at runtime, because it's a horrible idea.

Here's a list of the constraints I have always had in trying to implement this:
  • Don't know the image dimensions
  • Images in the "set" are different proportions
  • Need labels with each image
  • Images should be clickable
  • Image should automatically wrap inside of the containing block
  • We do know that all images "fit in a box" of a fixed dimension
Today I was implementing yet another site with this layout need, and decided to try to figure it out again. Thanks to this forum thread I was turned on to the idea of doing this by making the image a background image and using background-position: center to get the job done.

So, how'd I do it?

<div id="thumbBox">
    <div class="thumbUnit">
        <div class="thumbContainer" style="background-image: url(thumb1.jpg);">
            <a class="thumbLinkOverlay" href="/thumb1-link">&nbsp;</a>
        </div>
        <div class="thumbLabel"><div>Label 1</div></div>
    </div>
    <div class="thumbUnit">
        <div class="thumbContainer" style="background-image: url(thumb2.jpg);">
            <a class="thumbLinkOverlay" href="/thumb2-link">&nbsp;</a>
        </div>
        <div class="thumbLabel"><div>Label 2</div></div>
    </div>
</div>

The "thumbBox" is the containing block for the image set. Each "image" has an area blocked out for it by the "thumbUnit" div, which is given a FIXED width/height that is big enough to hold the largest image.

The "thumbContainer" has the desired image as a background image, since "background-position: center" works, whereas "vertical-align: middle" never does...

Since background images aren't clickable, we use absolute positioning to create a link tag that covers the entire "thumbUnit" area, making our image clickable.

The "thumbLabel" allows us to have a centered caption.

What's also great about this setup is that we can easily make the image set sortable with Scriptaculous, and make the image labels edit-in-place. I'll cover that in future post.

Downsides
There are a couple of downsides to this approach:
  • Images won't print in some browsers
  • Not all that semantic
However, in my case I am usually using this in an admin area where neither of these things matter. It might be possible to specify a print media css file to override the printing problem, but there isn't much you can do about the semantic problem.

If you really want good semantic markup for this situation, I suppose the best way is to just set up a Javascript function to "center" all of the images once loaded.

Below are the style definitions used with the markup.

<style type="text/css">
#thumbBox {
    float: left;
    width: 500px;
}
.thumbUnit {
    float: left;
    position: relative;
    margin: 5px;
}
.thumbContainer {
    width: 100px;
    height: 75px;
    background-repeat: no-repeat;                         
    background-position: center;                          
    position: relative;                                   
}                                                         
.thumbLinkOverlay {                                       
    width: 100px;                                         
    height: 75px;                                         
    position: absolute;                                   
    top: 0;                                               
    left: 0;                                              
}                                                         
.thumbLabel {                                             
    height: 1em;                                          
    text-align: center;                                   
}                                                         
</style> 

5 comments:

  1. Hey Alan:

    "Layout shown on the right?" You mean "above?" Either way, I still don't exactly get what you are showing. But knowing you I expect it to be sublime. :-)


    Also, why not store dimensions in the database?

    Lastly, still using Scriptaculous and not jQuery/UI? ;-)

    -Mike
    ReplyDelete
  2. Yeah, I fixed that "on the right" thing. Looked different in preview mode...

    It's not horrible to store dimensions in the database, just a lot of extra steps for something that is a PRESENTATION thing.

    The picture shows exactly what I'm going for. Getting different-sized images to line up nicely. It's a pretty common problem.

    Yeah, I still use prototype+scriptaculous. It works, it's stable, and I know it well. I am not saying jQuery isn't those things, I just haven't seen anyone make a compelling argument on why I should switch. I don't find prototype hard to use or verbose.

    -Alan
    ReplyDelete
  3. Why not use padding:auto on an img in your thumb container instead of using a background-image? I assume the background-image is what's causing failure to print in some browsers.
    ReplyDelete
  4. I've never tried padding: auto. Do you have example code to demonstrate your approach?

    The print issue is a "known problem" with the technique as stipulated above. It is still useful in some situations.
    ReplyDelete