Most web designers and developers know that creating column layouts, like used in newspapers, on websites is annoying. There are tons of gotchas and annoyances to deal with, and multi-column layouts for content are generally considered impractical. My goal is to turn a large block of content into a readable and cross-browser compatible mulit-column layout.

Step 1: Define what we're going to process

We need to have some way to tell the javascript functions that we'll be creating a column layout with this content. What I'm going to do is give the area a class of 'columns' so I can use prototype's getElementsByClassName function to gather all the content areas to process. I'm going to call my function that will do the actual work columnify and I'm going to transform the area into two columns. The javascript for that looks like this:

window.onload = function () { var columnAreas = document.getElementsByClassName('columns'); if(columnAreas.length > 0) { columnAreas.each( function (area) { columnify(area, 2); }); } }

Step 2: Gather the content and hide it

Next, I'm going to replace the entire DOM node with different elements, so I need to get the content inside the block, and I'm going to remove it from view so I can work with different kinds of data. At this point, the columnify function looks like this:

function columnify(area, num) { var children = area.childNodes; var length = area.innerHTML.length; Element.hide(area); }

The length property will be used when separating the content into the columns.

Step 3: Create a new place to put the content

Because I'm not using the area I just hid, I need a new place to put the content. I can think of a number of different ways to accomplish this; I could use a table, some floated divs, or absolutely positioning some elements. Using either tables or floated divs seems practical. For this project, I'm going to use tables to avoid extra css hacks and to make sure that my columns will display correctly in all browers. The javascript that gets added to the columnify looks like this:

new Insertion.After(area, '<table class="columned"><tr id="column_temp">' + '</tr></table>'); for(var i=0;i<num;i++) { new Insertion.Bottom($('column_temp'), '<td id="column_'+i+'"></td>'); }

I'll be replacing the id's on those elements as the last step in the process, they are just to make it easier to insert the content in my next step.

Step 4: Split the content into sections, and put it in the table

Now that I've got the new structure setup for the content, I need to split it up and insert it into the table. It would be possible to split the content on whitespace, and then insert words into the content area, but that could also split tags across different cells. So, what I'll do is iterate through each of the children of the area we're working with, and if it's a node (aka a tag), I'll just drop it right into the column. Then if the length of the column is greater than the average length, I'll move onto the next column. If the kind of node is just text, then I'll just separate up the content into equal parts and insert it into the columns. The rest of the javascript looks like this:

var column = 0; for(i=0;i<children.length;i++) { if(children[i].nodeType == 3) { var text = children[i].nodeValue; while(text.length > length/num) { pos = Math.round(text.length * (column + 1) / num); while(text.substr(pos, 1) != " ") { pos++; } var insert = text.substring(0, pos); new Insertion.Top($('column_'+column), insert); column++; text = text.substring(pos); } new Insertion.Top($('column_'+column), text); } else { new Insertion.Bottom($('column_'+column), children[i]); if($('column_'+column).nodeValue.length > length/num) { column++; } } }

It's interesting to note that when averaging, you have to increment the position reference, because if you decrement it, the last word may get left off on the final column.

Step 5: Get rid of the temporary id's

This part is quite simple, and it just removes the id's on the elements we created so that it doesn't muck with your javascript or subsequent columnify calls.

$('columns_temp').id = ""; for(i=0;i<num;i++) { $('column_'+i).id = ""; }

The final product:

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Quisque interdum risus at pede euismod egestas. Pellentesque a nibh. Suspendisse potenti. In vel eros. Vestibulum in nisl. Fusce aliquet pharetra felis. Praesent ac purus eu nibh ornare tristique. Pellentesque nec nunc. Proin blandit. Morbi vulputate mauris in nisi. Cras congue. Aenean posuere sem eget purus. In nec nisi id leo hendrerit rutrum. Aenean pede felis, placerat vitae, consectetuer at, dapibus et, urna. Sed auctor suscipit lacus. Phasellus scelerisque justo in orci. Phasellus orci lorem, mollis at, ultrices eget, luctus a, justo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam magna orci, laoreet vel, ultricies nec, laoreet at, sapien. Nulla ac tortor. Maecenas vitae pede quis odio iaculis tristique. Nam eleifend, sem nec molestie commodo, dui eros fringilla dolor, non dictum neque tortor sed tortor. Ut eros. Donec convallis pulvinar nisl. Maecenas convallis fermentum nulla. Ut aliquet, quam non tincidunt tincidunt, odio leo venenatis lacus, at rutrum nibh mi in sem. Aliquam magna ante, tempus eget, aliquam ut, vulputate et, diam.

Download the javascript