Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

balanced alternating column layout in CSS3

Tags:

css

I'm trying create a balanced (2-) column-layout.

The content is not text but blocks and varies in height. The content should be placed alternatingly left and right, as long as "left" and "right" have (roughly) the same height..

I.e. in this image: enter image description here The space between 1 and 3's shouldn't be there.

Or in this image: enter image description here the 2's should stand alone on the right side and the 1, 3's and 4 should stand on the left side (without space between them).

I tried using "floating <li>'s" like this:

HTML:

<ol class="context">
    <li class="gruppe">1</li>
    <li class="gruppe">2.0<br />2.1</li>
    <li class="gruppe">3.0<br />3.1</li>    
    <li class="gruppe">4</li>
</ol>

CSS:

ol.context 
{
  border: 1px solid #048;
  list-style: none;
  margin: 0;
  padding: 0 0 8px 0;
  overflow: auto;
}

li.gruppe
{
  background: #048;
  color: white;
  float: left;
  font: bold 32px Arial, sans-serif;
  margin: 1px;
  text-align: center;
  width: calc(50% - 2px);
}

(See attempt 1 and attempt 2)

I have also tried to use column's (column-count: 2; column-fill: auto;) but this does not fill the columns left-to-right first. (It fills top-to-bottom first.)

Is this even possible without JavaScript?

like image 412
Nils Avatar asked Jul 11 '13 15:07

Nils


Video Answer


2 Answers

I would say this is not possible without JS. Here is a fiddle I made based on an article from Ben Holland. At least to me looks like what you are after.

http://jsfiddle.net/QWsBJ/2/

HTML:

<body onload="setupBlocks();">
  <div class="block">
    <p>***Content***</p>
  </div>
  <div class="block">
    <p>***Content***</p>
  </div>
  <div class="block">
    <p>***Content***</p>
  </div>
  <div class="block">
    <p>***Content***</p>
  </div>
  <div class="block">
    <p>***Content***</p>
  </div>
</body>

CSS:

.block {
  position: absolute;
  background: #eee;
  padding: 20px;
  width: 300px;
  border: 1px solid #ddd;
}

JS:

var colCount = 0;
var colWidth = 0;
var margin = 20;
var blocks = [];

$(function(){
    $(window).resize(setupBlocks);
});

function setupBlocks() {
    colWidth = $('.block').outerWidth();
    colCount = 2
    for(var i=0;i<colCount;i++){
        blocks.push(margin);
    }
    positionBlocks();
}

function positionBlocks() {
    $('.block').each(function(){
        var min = Array.min(blocks);
        var index = $.inArray(min, blocks);
        var leftPos = margin+(index*(colWidth+margin));
        $(this).css({
            'left':leftPos+'px',
            'top':min+'px'
        });
        blocks[index] = min+$(this).outerHeight()+margin;
    }); 
}

Array.min = function(array) {
    return Math.min.apply(Math, array);
};
like image 117
Roope Avatar answered Nov 08 '22 11:11

Roope


Updated: I believe this is almost impossible to achieve with CSS only. There are many different solutions, but they all require some compromises unless you are willing to use JavaScript or some server-side code.

Using CSS columns

Here's an alternate fiddle using reordered blocks. Here's a fiddle demo using CSS columns without reordering.

You can use CSS colunms to change your block flow to vertical unless you alter the order of their output. If you can output odd numbers first, then even numbers, you win.

<div class="wrapper">
  <div class="block1">1</div>
  <div class="block3">3</div>
  <div class="block2">2</div>
  <div class="block6">4</div>
</div>

.wrapper {
    column-count: 2;
    column-width: 100px;
    -moz-column-width: 100px;
    -webkit-column-width: 100px;
    width: 260px;
}
div {
    border: 1px solid #999;
    display: inline-block;
    margin: 10px;
    width: 100px;
}
.block1 { height: 100px; }
.block2 { height: 130px; }
.block3 { height: 150px; }
.block4 { height: 100px; }

This solution is not compatible with IE9 and below.

Block Height Known

If you do know your block heights you can solve this problem by using absolute positioning.

block1 {
  height: 100px;
  position: absolute;
  left: 0; 
  top: 0;
}

block2 {
  height: 110px;
  position: absolute;
  left: 0; 
  top: 100px; /* The height of the div above it */
}

A big drawback is dynamic content; we seldom know block height. So this solution is very limited in its application unless you are willing to calculate the height block height.

If you are willing to use JS

Use a plugin like Masonry. Both in vanilla js or jQuery flavour.

Other Options

This leaves you with the following options that require some compromises.

  1. Group your blocks into columns. See this Fiddle for a demo. This will alter the flow of your blocks to vertical, then horizontal.

  2. Use display: inline-block; vertical-align: top; on your blocks. This will leave some white space below your blocks.

  3. Force the height of your blocks, rendering this a non-issue. For blocks with additional content use the overflow property to allow in-block scrolling.

  4. As others have commented, you could attempt to calculate the height of the blocks on the server.

like image 41
Mohamad Avatar answered Nov 08 '22 12:11

Mohamad