#masthead{
border:1px dashed #333;
width:0;
position:absolute;
height:100px;
top:0;
}
#left{
border:1px dashed #000;
width:252px;
}

#center{
border:1px dashed #666;
margin:.5em 0 0 0;
padding:1em;
position:absolute;
top:100px;
}

The Breakdown - Dynamic widths explored

Problem:

A columnar styled page where one or more of the left hand columns have a fixed width with the right column(s) consuming the rest of the width of the page, aka "liquid". The right hand 'div' object(s) fails to fill the remainder of the page or exceeds the window width causing the user to scroll right. Note: This problem does not exist, however, if you can guarantee that the content of the remaining right hand column(s) fills the entire width of the dynamic column(s).

This problem also assumes the usage of positioned objects and not the usage of floats. There are arguements for using floats over positioning and vice versa. A strong argument against floats is how the page will display chronologically in a non-standards compliant browser or a text-only browser. My home page, which uses positioned element and this solution, displays in a logical order in these types of browsers. The order is, from top to bottom, the masthead object, left object, center object, right object, links object and footer object.

Example:

View this page to see how different width values are handled for positioned objects. Continue reading for explanation.

Solution:

First, honorable mention should go to Eric Costello of glish.com who has addressed this issue using 100% CSS and no JavaScript. Eric has clearly demonstrated his understanding of CSS implementation and the ability to think out of the box. My solution requires the use of JavaScript. I will leave it up to you the reader to decide which method is best suited for you.

BONUS: My code addresses workarounds for browser misinterpretation of object widths. See Box Model Hack for further explanation on about browser width misinterpretation.

Let's get started! We will use this page as our example. Our left column object is fixed at 252px including padding, borders and margins. Our goal is to have the masthead fill the remainder of the page or flush with the right side of the browser window when using positioned objects. Notice that the CSS for the masthead does not have a width attribute and value. In theory the right border of the masthead should not render flush with the browser window as seen above, but, rather with its contained content as rendered here. This also applies for the width value of 'auto'. If we declared a width of 100% for the masthead object so that it fills the remainder of the page, it would exceed the width of page (see rule in following paragraph) as seen here. We need to configure the masthead object so it sizes dynamically to the remaining space.

Rule #1: Defining a width

To dynamically size an objects width, we should first understand how an object obtains its width.

RULE:
The width of an object - or column, for the purpose of this discussion - when declared as a percentage, the percentage is based against size of that objects parent.

So, if the far right column has a width of 65% and its parent is the browser window, the right column's width in a 1024px window is 657px, in a 800px window it is 520px and a 600px window it is 390px.

Algebra and CSS together?

Still don't see the problem? Let's break it down to the 3 individual objects: the left column, the right column and the browser window. The ideal scenario is to have the sum of the widths of left column and the right column always equal the width of the browser window, regardless of the content in the columns. On this page, the left column is 252 pixels wide. The right column width can be determined by subtracting the left column from my monitors resolution width of 1,280px. Thus the right column's width should be 1,280-252=1,028px. However, you know as well as I that not everyone's resolution is 1,280x1,024. (As Sep. 2002, about 50% view at 800x600.) Obviously using a fixed width for the right column is not the answer, so we must use the relative measure of a percentage.

To find the percentage, we need to dust off our old high school algebra book. With a little research we re-learn the formula of (x-y)x = z(100) or (1280-252)/1280*100=80%. Huh? Translated into english, we subtract from our resolution width (1280) the left column width (252) then divide the difference by our resolution width (1280) which equals the right column width as a percent in decimal form. Multiply that by 100 and you get a whole number (80%). Now that we know the percentage, we can assign that value to the column's style and call it day, right? Yes, but it's not quite Miller time yet.

Re-visiting the rule above about defining the widths of columns, we see that this percentage is based on the width of the browser window. As the browser window width changes in size, the percent doesn't change with it - it is hard coded into the style. True, using a percentage for the width of the right column will size that column thinner or wider as the browser window contracts or expands, but 80% isn't always 80%.

When 80% isn't 80%.

Using our algebraic statement above, we can see that as our browser window width changes, so must our style's width. Figure 1 shows the breakdown of the right columns width based in different browser window widths.

Fig. 1
Browser Window Width (px) Left Column Width (px) Right Column (px) Right Column (%)
1600 252 1348 84
1280 252 1028 80
1024 252 772 75
800 252 548 69

As you can see, things only gets worse as resolutions grow or when users view your site in a restored window (not maximized). This discrepancy either leaves an unsightly gap between the right columns right edge and the browser window, or extends beyond the browser windows right edge forcing the user to scroll left and right. Again, this can be illustrated here with the bottom row.

The fix is in

Luckily, CSS and JavaScript can live together in harmony and with a little bit of code and math, our problem is solved. By using JavaScript, we can determine the user's browser window width. From that we will subtract our fixed column's width and pass to our CSS the difference, which will be used as our right column's width - or in this example the masthead's width.

The script

It may look like a lot of code, however, you will find that we are simply addressing the same document objects multiple times. Once to get it's value and again to send it a new value.

window.onresize = findWindowDim;

function findWindowDim(){

var dbcW = document.body.clientWidth;
var objLeft = document.getElementById('left');
var objMasthead = document.getElementById('masthead');
var objCenter = document.getElementById('center');
var objRight = document.getElementById('right');
var objLinks = document.getElementById('links');
var leftWidth = document.getElementById('left').offsetWidth;

var mastheadXPosPx = leftWidth+"px";
objMasthead.style.left = mastheadXPosPx;

var centerXPosPx = leftWidth+"px";
objCenter.style.left = centerXPosPx;

if (dbcW > 252){

function hideError() {
return true;
}
window.onerror = hideError;

mastheadWidth = dbcW-leftWidth;
mastheadWidthPx = mastheadWidth+"px";
objMasthead.style.width = mastheadWidthPx;

centerWidth = .64*mastheadWidth;
centerWidthPx = centerWidth+"px";
objCenter.style.width = centerWidthPx;
newCenterWidth = objCenter.offsetWidth;

rightX = newCenterWidth+leftWidth;
rightXPosPx = rightX+"px"
objRight.style.left = rightXPosPx;
objLinks.style.left = rightXPosPx;
rightWidthPx = dbcW-leftWidth-newCenterWidth-14+"px";
objRight.style.width = rightWidthPx;
objLinks.style.width = rightWidthPx;
}

var wiW = window.innerWidth;

if (window.setCursor && (wiW >252)){

function hideError() {
return true;
}
window.onerror = hideError;

mastheadWidth = wiW-leftWidth;
mastheadWidthPx = mastheadWidth+"px";
objMasthead.style.width = mastheadWidthPx;
centerWidth = parseInt(.64*mastheadWidth);
centerWidthPx = centerWidth+"px";
objCenter.style.width = centerWidthPx;
newCenterWidth = objCenter.offsetWidth;
rightX = newCenterWidth+leftWidth;
rightXPosPx = rightX+"px"
objRight.style.left = rightXPosPx;
objLinks.style.left = rightXPosPx;
rightWidthPx = wiW-leftWidth-centerWidth+"px";
objRight.style.width = rightWidthPx;
objLinks.style.width = rightWidthPx;
}

<body onload="findWindowDim">

The CSS used in conjuction with this can be found at the top of each object on this page.

How does it work?

We need to run the function findWindowDim() anytime the user resizes their browser window. The onresize event handler will handle this for us.

window.onresize = findWindowDim;

Start the function and declare a list of variables.

function findWindowDim(){

Document.body.clientWidth finds the width of the browser window for IE, Mozilla and Netscape 7.

var dbcW = document.body.clientWidth;

Creating some shorthand variables for the DOM

var objLeft = document.getElementById('left');
var objMasthead = document.getElementById('masthead');
var objCenter = document.getElementById('center');
var objRight = document.getElementById('right');
var objLinks = document.getElementById('links');

Get the width of our fixed column. Even though we know the width of the fixed column to be at 252px and could simply set our leftWidth to 252, letting the browser determine this width makes up for the Box Model Hack problem. We will continue this logic throughout the script. This allows for the JavaScript driven CSS to be flexible to each browsers interpretation - or misinterpretation - of widths.

var leftWidth = document.getElementById('left').offsetWidth;

Set the widths of the masthead and center objects. First line concatenates "px" to the value so the second line can correctly set the objects new style value. Again, even though we know the left value of the 2 absolute positioned columns, we use the browsers interpretation of the left column width determine the left position.

var mastheadXPosPx = leftWidth+"px";
objMasthead.style.left = mastheadXPosPx;

var centerXPosPx = leftWidth+"px";
objCenter.style.left = centerXPosPx;

The 'if statement'. The value of 252 is the fixed width of the left column though this value is arbitrary. In this example I have displayed the value for illustration purposes only (see following function). However, setting a value in the 'if statement' will allow for an 'else statement' to trigger. For example, if the user were to reduce their browser window's width to less than, say, 450px, the else statement could be used to set new style values for the page's columns (I.e.: pseudo minimum column widths.)

if (dbcW > 252){

The hideError() function is used to stop the script from displaying an error message when the browser's window width is less than the sum of all the fixed width elements on a page. This includes scenarios such as we have on this page. This column contains a table of which can only become so narrow before it's minimum width is reached. The sum of the width's of the left column and center column is now the JavaScript's "minimum width" of this page. With out the hideError() function, the script would generate an error when the width of the browser window is less than that sum. Though in theory, an objects style can have a negative width, JavaScript will not pass a negative width to a style.

This function may be left out only if you are 100% sure of the page's minimum width AND set that minimum width value in the 'if statement' above. Given the chance of an error to develop in this scenario, especially with dynamically generated data, I highly recommend using the function.

Window.onerror checks to see if a non-syntatical error has been generated in the script. When the error is generated, call the function, and return a value of true. This will suppress the error, hide any visual notification to the user, and allow the script to continue. NOTE: Do not add this function until you are completely finished with the script and have determined it doesn't not cause any other errors. We don't want to use this function to hide sloppy mistakes within our code.

function hideError() {
return true;
}
window.onerror = hideError;

Dynamically size the width of the masthead column by subtracting the left column from the browser's window width. Concatenate the value with "px" and set the style with our abbreviate DOM. This process is repeated for each column to be sized.

mastheadWidth = dbcW-leftWidth;
mastheadWidthPx = mastheadWidth+"px";
objMasthead.style.width = mastheadWidthPx;

Set the width of the center column. .64*mastheadWidth returns in essence 64% the width of the masthead. Parsing this number rounds the value to a whole number, though this is not required. This expression could have just easily been written (dbcW-leftWidth)*.64 but I felt like using the variable I declared above instead. Altering the equation used to define centerWidth will allow you to set the width as a percent or even fixed in width. Play with it and have some fun.

Just as we set the new width of the center column, we need to get the new width back - newCenterWidth - so we can use it later.

centerWidth = parseInt(.64*mastheadWidth);
centerWidthPx = centerWidth+"px";
objCenter.style.width = centerWidthPx;
newCenterWidth = objCenter.offsetWidth;

Lastly, we need to position and set the widths of our last column which contains 2 objects: right and links. Since links has the same left and width values of right, we set link's values equal to right's.

rightX = newCenterWidth+leftWidth;
rightXPosPx = rightX+"px"
objRight.style.left = rightXPosPx;
objLinks.style.left = rightXPosPx;
rightWidthPx = dbcW-leftWidth-newCenterWidth+"px";
objRight.style.width = rightWidthPx;
objLinks.style.width = rightWidthPx;
}

We need to repeat the entire process for Netscape 6. The proerty window.setCursor is exclusive to Netscape 6 and including it within the 'if statement' will filter our Netscape 7/Mozilla 1 which also recognize window.innerWidth.

var wiW = window.innerWidth;
if (window.setCursor && (wiW >252)){
etc.......

On load of the page, we need to run the function to set all of the objects in place.

<body onload="findWindowDim">

Unlimited possibilities.

The best part of this script is centerWidth = parseInt(.64*mastheadWidth);. This is where all of the fun begins. Altering the equation to determine widths can allow for some complex designs. You can set fixed widths, dyanamic widths + some fixed value, minimum widths, widths that grow exponetially, etc... This should keep you busy for a while, but if you are ready for more, we can translate this into dynamic row heights.

[an error occurred while processing this directive]