Equal column heights with JQuery
Posted by Howard Richardson (comments: 0)
Line 'em up!
One of the common problems encountered when you ditch the 2000-era nested tables method for page layouts and move to 100% CSS is vertical alignment. It used to be a trivial matter to make uneven columns match up because the table cells would automatically expand to fill the space given, but how can you do this when your "columns" are just stacked floating DIVs, one on top of another?
There are plenty of quick Javascript and JQuery fixes out there that simply adjust the height of the container column in one go, but these won't work for my purposes. Because each of my columns contains any number of content boxes, adjusting the column DIV wont actually make those boxes fit the space any better. What we need here is some kind of dynamic padding feature.
Display:table?
Fixed height items within the column DIVs would do the trick, but doesn't allow you much flexibility.
Another alternative might be to ape the whole table structure using DIVs with the CSS attributes "display:table-cell" and "display:table-row" in classes, but I've found that to be unreliably supported across different browsers when I've tried it.
Perhaps I didn't do it correctly or perhaps that's something that's now fixed in the latest browsers, but let's face it - you're using a table in all but name! Somehow that seems against the spirit of finding a solution, though I'd agree it's an improvement on using an actual table.
Dynamic padding with JQuery
This seems to be an excellent candidate to fix with JQuery, so I gave it a shot. Here's the site I worked on: Lawbore City Hub. It has two fixed-width DIV columns (div.hubcolumnleft and div.hubcolumnright) with several variable height boxes in each one. Each of the boxes is also a DIV (div.hubbox).
To make the columns the same initial height, you'd just have to vertically pad the space in the boxes in the shorter column until they match up. I decided that for this to look good the padding would have to be evenly distributed across all the boxes.
So we have a basic formula here:
Add X pixels of padding to boxes Y1...Yn, where X= (A-B)/n
A is the height of the highest column, and B the height of the smaller column.
Using JQuery dimensions it was easy to find out the heights of the columns themselves, like this:
function makeEqualHeight (col1,col2) { $(col1).find('.hubbox').last().css('margin-bottom',0); $(col2).find('.hubbox').last().css('margin-bottom',0); var height1=col1.height(); var height2=col2.height(); ...
As you can see, one of the issues that cropped up once we intend to switch to fixed-height columns is that there was a difference in bottom box margins depending on column height, and this was throwing the height calculation off. For this purpose, we removed the bottom margin of each column's last box, so that they lie flush to the bottom of the column DIVs.
Next job is to find the difference between the two columns:
var difference; var tmp; if (height2 > height1) { // swap so col1 always bigger tmp=col1; col1=col2; col2=tmp; tmp=height1; height1=height2; height2=tmp; } difference=(height1-height2);
Because we don't know which column will be the tallest always, we have to allow for the fact that they might need to be swapped around before doing the subtraction. We do the comparison and swap the cols if necessary (and swap their height measurements, of course).
Now we know how many extra pixels we have to distribute across the shorter column's boxes (ie. the difference). The next step is to find out how many boxes these pixels will be distributed across. In my site the boxes have the CSS class 'hubbox', so we can count these...
var boxes= $(col2).find('.hubbox'); var noBoxes = boxes.size();
I've hard-coded this '.hubbox' class into my routine because it's never likely to change on my site, but you could pass this as a parameter to the function if you wanted instead (or maybe just use find('div') instead to match any child divs?). In any case, you'll want to change it to match whatever class your column boxes are.
Now all that remains is to add each box's share of the pixels to some part of its padding. I elected to add them at the bottom of the box, which seems the most obvious choice. We do this like so...
boxes.each(function(foo){ var padding = Math.floor(difference / noBoxes); var leftover = difference-(padding * noBoxes); var add_leftover = 0; // Add leftover pixel (if any) to first box if (foo==0) add_leftover=leftover; var current_padding=parseInt($(this).css('padding-bottom')); var new_padding=current_padding+padding+add_leftover; $(this).css('padding-bottom',new_padding+"px"); }
As you can see there's one final extra thing that needs to be taken care of here. Because most browsers won't process fractions of pixels, when you divide the padding between the boxes there's a chance that a fraction of a pixel will be left over and ignored. These fractions add up across the boxes and result in the bottom box being one pixel too short.
So what we do is calculate if there is a leftover pixel and add it onto the padding of the first box. As you can see the add_leftover variable only gets set when the index of the 'each' loop is zero, ie. on the first item. This ensures our leftover pixel doesn't get added multiple times!
And voila, we can now magically equalise the height of any unequal columns by calling the function like this...
makeEqualHeight ($('.hubcolumnleft:first'),$('.hubcolumnright:first'));
I added the ":first" selector onto these just to be on the safe side, because you only want to pass one object, but actually it's not needed. In most designs these would be #id values anyway so you'd probably just do...
makeEqualHeight ($('div#leftcol'),$('div#rightcol'));
The finished code
Here's the function in its entirety. Don't forget to change the instances of ".hubbox" if you're going to use it yourself!
function makeEqualHeight (col1,col2) { // First remove bottom padding on last hubboxes... $(col1).find('.hubbox').last().css('margin-bottom',0); $(col2).find('.hubbox').last().css('margin-bottom',0); var height1=col1.height(); var height2=col2.height(); var difference; var tmp; if (height2 > height1) { // Swap so col1 always bigger tmp=col1; col1=col2; col2=tmp; tmp=height1; height1=height2; height2=tmp; } difference=(height1-height2); var boxes= $(col2).find('.hubbox'); var noBoxes = boxes.size(); boxes.each(function(foo){ var padding = Math.floor(difference / noBoxes); var leftover = difference-(padding * noBoxes); var add_leftover = 0; // Add leftover pixel (if any) to first box if (foo==0) add_leftover=leftover; var current_padding=parseInt($(this).css('padding-bottom')); var new_padding=current_padding+padding+add_leftover; //alert (new_padding); $(this).css('padding-bottom',new_padding+"px"); } ); }
Enjoy your equal height columns!
You can see the code working at Lawbore City Hub. Comments welcome, as always :-)
Add a comment