As an alternative to “faux columns” and other not-so-clean methods for attaining equal column height in CSS layouts, this tutorial will explain how to accomplish equal columns with pure, unobtrusive JavaScript. This method takes into account top and bottom border thickness as well as interior padding so that the two columns are exactly the same height in virtually every circumstance. And the script is written to ensure that the columns will equalize regardless of which one is taller, so it’s very practical for dynamic content with complex background styles.
This is a full tutorial that expands on a previous post where I outlined a very rudimentary method to achieve equal columns using JavaScript. In the previous article, I discussed the various benefits and drawbacks to using JavaScript for this issue, so I won’t repeat those here.
Enjoy!
Step 1
The HTML for this example will look like this:
<div id="left_column"> <p>Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column. Lorum ipsum text to fill up the left column.</p> </div> <div id="right_column"> <p>Lorum ipsum text to fill up the right column. Lorum ipsum text to fill up the right column. Lorum ipsum text to fill up the right column. Lorum ipsum text to fill up the right column. Lorum ipsum text to fill up the right column. Lorum ipsum text to fill up the right column. Lorum ipsum text to fill up the right column.</p> </div>
And here is our CSS code:
#left_column { float: left; width: 180px; border: solid 1px #ccc; padding: 10px; margin: 0 20px 0 0; } #right_column { float: left; width: 180px; border: solid 1px #ccc; padding: 20px; }
The key values to take note of in the CSS are the border thickness (1px) and padding (20px), as these will factor into our calculations in order to help us decipher the true height of both columns. With this markup and CSS in place, your HTML output would look something like this:
As you can see, we have two columns with different heights, due simply to the fact that the column on the left has more content. Now, we could just apply a height in our CSS to our right column to equal the left, but that would not provide a very good solution for dynamic content, and certainly would not be a very good foundation for a 2-column template used on multiple pages.
Step 2
Let’s dip into our JavaScript file, and start making the calculations. First we’ll place each of our columns in a variable using getElementById
so we can manipulate them easily:
var myLeftColumn = document.getElementById("left_column"); var myRightColumn = document.getElementById("right_column");
Next, we get the full height of each column by accessing each column’s offsetHeight
property:
var myLeftHeight = myLeftColumn.offsetHeight; var myRightHeight = myRightColumn.offsetHeight;
The height given by the offsetHeight
property is the height of the content area, plus any top and bottom padding, plus any top and bottom border thickness. We cannot change the offsetHeight
property of an element, we can only read its value, which will assist us in subsequent steps.
Step 3
Now here’s where it gets a little tricky. At this point we want to read the exact values of the borders and padding so we can use them as part of our calculations, and, of course, keep them intact. JavaScript allows us to access the value of a particular CSS element using the style
object, like this:
var myTopBorder = myLeftColumn.style.borderTopWidth;
However, the style
object only gives us access to inline CSS or CSS that has been set via JavaScript. So, in the above example, the variable “myTopBorder” will not have a value, because the CSS we’ve set for our columns is set in an external style sheet. In order to obtain the value of specific styles, regardless of where they are declared, we need a cross-browser function that accesses a document’s actual rendering space. Fortunately, this type of thing has already been done before. Below is the code we’re going to include in our JavaScript file to help us get what’s called the “Computed Style” of an element. There are many variations of this type of function on the web. This one is from the book The JavaScript Anthology by James Edwards and Cameron Adams:
function retrieveComputedStyle(element, styleProperty) { var computedStyle = null; if (typeof element.currentStyle != "undefined") { computedStyle = element.currentStyle; } else { computedStyle = document.defaultView.getComputedStyle(element, null); } return computedStyle[styleProperty]; }
Here’s a run down of what this code is doing: Our function takes two parameters: The element that we’re accessing (in this case it would be either the “left_column” div or “right_column” div), and the property we want to read. In our current tutorial, this might be the top padding. Inside our function we declare a local variable computedStyle
, which will eventually store the “returned” value.
Then we have a simple if
statement that handles the two methods used by current browsers to access the document’s rendering space. Under W3C specifications, the computed style is accessed by means of the defaultView
object, which we see in the latter part of the code branch. But Internet Explorer uses a proprietary element property currentStyle
. Our code branch first checks to see if the currentStyle
property has been defined. If it has been defined, then we know the user is viewing in Internet Explorer, and so our computedStyle
variable stores the element (passed in by the first parameter) with the proprietary currentStyle
property attached to it, in preparation for the return value at the end of the function.
If the first if
statement fails, then we know the browser is not Internet Exlplorer, so the else
statement is executed, and the element’s computed style is accessed by running the getComputedStyle
method on the current document’s defaultView
object.
Finally, after storing the current element’s style object in the computedStyle
variable, the actual value of the style we want to obtain is “returned” when the function completes processing. In this case it might be “1px”, if we had passed in the “borderTop” property.
Step 4
Now that we have a cross-browser method to read the style of any element on the page, let’s use this function to find out the exact amount of any borders and padding present on both columns:
var myLeftBorderTopPixels = retrieveComputedStyle(myLeftColumn, "borderTopWidth"); var myLeftBorderBottomPixels = retrieveComputedStyle(myLeftColumn, "borderBottomWidth"); var myLeftPaddingTopPixels = retrieveComputedStyle(myLeftColumn, "paddingTop"); var myLeftPaddingBottomPixels = retrieveComputedStyle(myLeftColumn, "paddingBottom"); var myRightBorderTopPixels = retrieveComputedStyle(myRightColumn, "borderTopWidth"); var myRightBorderBottomPixels = retrieveComputedStyle(myRightColumn, "borderBottomWidth"); var myRightPaddingTopPixels = retrieveComputedStyle(myRightColumn, "paddingTop"); var myRightPaddingBottomPixels = retrieveComputedStyle(myRightColumn, "paddingBottom");
Above we have 8 different variables representing the top and bottom styles we’ll be using in our calculations. The variable names we’re using should be self- explanatory as to what each represents.
Step 5
Next, we want to add up these “extra” values so the complete “extras” will be available in the final part of our code. But remember, the values we’re dealing with above are actual CSS values, which, in this case, have unit declarations attached to them. For example, the thickness of the top border will not just be “1” — it will be a string (not a number!) reading “1px”. To deal with this, we want to do two things with each of those values: (1) Strip the “px” off, and (2) convert the variable to a number, so that JavaScript will understand that we want to “add and subtract” (which we do with numbers) not “concatenate” (which we do with strings). Here is the code to accomplish this:
var myLeftBorderNumber = Number(myLeftBorderTopPixels.replace("px", "")) + Number(myLeftBorderBottomPixels.replace("px", "")); var myLeftPaddingNumber = Number(myLeftPaddingTopPixels.replace("px", "")) + Number(myLeftPaddingBottomPixels.replace("px", "")); var myLeftExtras = myLeftBorderNumber + myLeftPaddingNumber; var myRightBorderNumber = Number(myRightBorderTopPixels.replace("px", "")) + Number(myRightBorderBottomPixels.replace("px", "")); var myRightPaddingNumber = Number(myRightPaddingTopPixels.replace("px", "")) + Number(myRightPaddingBottomPixels.replace("px", "")); var myRightExtras = myRightBorderNumber + myRightPaddingNumber;
The values to take note of here are contained in the variables myLeftExtras
and myRightExtras
. You’ll see why we need these values in our final step.
Step 6
Now we refer back to the original variables where we stored the full height of each of the two columns, and we do a simple test to see which of the two columns is taller. Once we know which column is taller, we change the height of the shorter column to match the bigger one, factoring in our “extras”:
if (myLeftHeight > myRightHeight) { myRightColumn.style.height = (myLeftHeight - myRightExtras) + "px"; } else { myLeftColumn.style.height = (myRightHeight - myLeftExtras) + "px"; }
So, according to the above code, if the left column is bigger than the right, the right column’s height property is adjusted via the style object that we mentioned earlier. But we can’t just change the height of the right column to be equal to the left. The value of the right column’s height needs to be the same as the left minus the “extra” values that we calculated from the right column. Otherwise, the height of the right column would end up bigger. The “else” statement simply allows us to do the opposite — access the left column’s height value when the right column is taller. And finally, the “px” is appended to each final number, in accordance with CSS rules for value declarations.
That’s it. All you need to do is attach the entire code as a function that runs when the DOM is ready. And to make it easy to use the script on different pages, simply add two editable variables at the top of the function, like this:
var myLeftColumnId = "left_column"; var myRightColumnId = "right_column";
This allows you to add your JavaScript file to any page by simply plugging in the IDs for the columns that you want to equalize. And don’t forget to change the two lines shown in step 3 to include the newly added “editable” variables:
var myLeftColumn = document.getElementById(myLeftColumnId); var myRightColumn = document.getElementById(myLeftColumnId);
The final result, as displayed in your browser, is shown below:
Add more content to either of the columns, or change the borders or padding, and you’ll see that the columns will always be the same height. Although this example is using simple CSS borders to display the columns, this code really comes in handy on 2- or 3-column layouts that require background images or background colors to extend to the footer area in non-static pages. Of course, this specific code only equalizes two columns, but a similar method could be followed to ensure that a third column has the same height.
View the demo, or download the finished code below. The final example also includes a ‘listener’ that runs the main function when the DOM is ready.
I was also forced recently to rely upon Javascript to set column heights. My client had engaged my services to not only make their site accessible (a blind receptionist was to start within 4 weeks), but to move their site from a tables based layout to that using CSS. Their website’s middle had a news area comprising 3 columns, 2 with one b/g colour, the third in another. The website had the additional complexity that its width was not fixed by rather dynamic, min-width of 950px and max-width of 1200px (at request), thus I was forced to develop code that handled the likelihood of the user adjusting their browser’s width.
If anyone is interested in reading my post (uses jQuery) you can view it at http://notsurereally.wordpress.com/2009/02/07/jquery-adjusting-div-heights-on-window-resize/
I was recently criticised for using javascript to handle layout, but as I explained in a reply to their comment I didn’t have a great deal of choice given Internet Explorer 6 & 7 do not support display:table, display:table-row or display:table-cell. Thankfully with IE8 on the scene there is light at the end of the tunnel, we can only hope that its adoption will be far quicker than before, or that they move to Firefox, Safari or Chrome.
I prefer to collect the column heights in an array then use math.max() to get the highest.
This is a great script, thank you. I’ve just spent a week trying to figure out why my old script would not display a page properly on browsers other than Explorer, for a new site I’m building.
I finally discovered the problem was a padding issue which the script was not taking into account. So now I have a great new script for equal columns and life is good once more! Thanks again.
I was excited at first. I tested it out on Mozilla and it worked perfectly :) But when I tried it on IE8, nothing :(
Same deal as K-Nine. This works in Safari/Firefox/Chrome but my tests of IE8 and IE7 failed, with an error that starts at the line:
myRightColumn.style.height = (myLeftHeight – myRightExtras) + “px”;
Its working fine in Mozilla but in IE7 retrieveComputedStyle() function is not working properly.
@Sanjay:
I’m not sure why it’s not working for you, but I assure you it works fine in all versions of IE.
I have no words to express my grattitude. It worked great with my site, you actually make my day pal.
Thanks very much.
I found two issues that caused erratic IE behaviour:
If you have specified a unit in ems, it won’t work as IE returns the original unit, not a computed value. Using “runtimeStyle” might be a solution, though I haven’t tested it, supposed to be a bit buggy.
If a value is not set in any level of the cascade, IE returns something else, don’t know what but when I didn’t have any borders set, computedStyle[“borderTopWidth”] had a value of “medium”.
Mikael,
I don’t see any of the problems you’re seeing. I tested in IE6, 7 & 8, and in each case specifying a height in ems for one or both columns works fine.
And I’m not sure what you mean exactly when you say “in any level of the cascade”. The only values that are set are in the CSS. The JavaScript should not be changed. Whatever unit is used, the JS will compute based on pixels, and change accordingly, by pixels only.
If you could clarify the problems you’re having, that would help. Thanks.
Did you try with padding set in ems?
I only tried IE8, but an alert of the retrieved value from a div with a top padding of 0.5em with
document.defaultView.getComputedStyle(element, null)[“paddingTop”]
returned just the string “0.5em”, which if I’m thinking correctly, would be hard to match to pixelheights.
What I meant with the “cascade” was that if a value is not set in either an external css, inline style or element style it seemed that IE returned some strange value as an alert of
document.defaultView.getComputedStyle(element, null)[“borderTopWidth”]
returned “medium”…
I’m on XP btw…
Sorry, it was in IE7…
Ah, I see what you mean now. Using ems for the padding does cause JS errors in IE6 and IE7, but IE8 seems to be working fine. I had completely forgotten about changing the padding, it’s been a while since I wrote this script.
Interesting. Well, I’m not sure if it’s worth updating, considering both those browsers are on the way out, but thanks for the heads up, which should help users avoid using ems on the padding values (which I think is rare anyhow, no?).
This script does not like CSS styles. i.e. Changing the boarder styles will cause the jquery to fail in IE7&8:o(
I really wanted to use this on my site but can’t get it to work :( I’ve edited the code to match my css div names and everything. I’m hoping you’ll see this comment in the hopes that maybe you can tell me what I’m doing wrong (http://harrimanpcrepair.com/)
I’m not good with JS it’s a little past my level of work.
Here is one that can adapt to the number of columns on responsive websites: https://github.com/Sam152/Javascript-Equal-Height-Responsive-Rows