12

I thought this would be (relatively) easy, but from the answers it seems harder than I anticipated.

Perhaps even impossible!

GOAL

I'd like to have a large list of div elements that can arbitrarily be assigned a .left or .right class.

All the .left divs should stack up underneath each other on the left hand side, the .right divs should stack up underneath each other on the right hand side. The number of divs in each class is arbitrary.

enter image description here

THREE CONDITIONS

  1. The height of each div will not be known in advance

  2. I would like them to stack up underneath each other on the assigned side, regardless of how many divs are present, what order they appear in, and how many are assigned to either side.

  3. I don't want to use a 'wrapper' div as some have suggested. This is because the solution must cater for random quantity and ordering of .left and .right divs (see example below).

Ideally I'd like it to be a pure html / css solution, as backwards compatible as possible - though I realise this may prove unrealistic.

SAMPLE HTML

<div class="left">left one</div>
<div class="left">left two</div>
<div class="right">right one</div>
<div class="left">left three</div>
<div class="right">right two</div>
<div class="right">right three</div>
<div class="left">left four</div>
<div class="right">right four</div>
<div class="right">right five</div>
<div class="left">left five</div>
<div class="right">right six</div>
<div class="right">right seven</div>

UPDATE

After so many answers I'm impressed by the range of answers/techniques, but none of them quite meet all of the conditions.

Hence I'm staking a quarter of my meagre reputation on trying to get a solid solution!

UPDATE 2

It seems that my original goal is undoable. Therefore I have not marked any as the answer, although having put up a bounty I awarded it to Josh who gave me the best way of (almost) achieving that, along with great explanations of the css he used.

Thanks everyone for your help and ideas.

Martin Hansen Lennox
  • 2,837
  • 2
  • 23
  • 64
  • Can you put all the left divs in one container and all the right divs in another container? – JakeParis Nov 18 '14 at 01:41
  • No, I would like to control the order on a small screen, yet be able to assign them left or right on a full screen, as in the example I gave – Martin Hansen Lennox Nov 18 '14 at 01:42
  • 1
    Why is this downvoted? If it's a bad question, please explain why or how can I improve it? – Martin Hansen Lennox Nov 18 '14 at 01:43
  • I think the best solution is to create separate presentations for wide (all left columns together) and narrow screens (left and right columns in desired order) and display the appropriate one using media queries. – Salman A Nov 24 '14 at 11:31
  • @SalmanA I might end up using .hideonfullscreen and .hideonmobile classes. The trouble is realising that I probably can't do what I originally intended makes me what to do it more. – Martin Hansen Lennox Nov 26 '14 at 14:26
  • Nice question, made me read http://designshack.net/articles/css/everything-you-never-knew-about-css-floats/ to understand why the floats don't "float to the surface". – hon2a Nov 27 '14 at 13:10
  • 1
    Maybe something like jQuery Masonry would help. http://masonry.desandro.com/ – jim31415 Nov 27 '14 at 15:40

5 Answers5

9

Here's what I'd recommend:

Fiddle requiring no hacks

Here's the key CSS that we're including:

@media (min-width:400px){
    .left {
        width: 60%;
    }
    .right {
        width: 30%;
        float: none;
        margin-left: 100%;
        transform: translateX(-100%);
    }
}

Explanation of this CSS

What that's doing is pushing your .right elements completely out of the container to the left, and then dragging them back their entire width.

The position: relative; and left: 100%; tell the element that it needs to display off the right edge of the container.

The transform: translateX(-100%); then tells the element that it needs to display to the left (hence the negative) 100% of its width - which drags it to be flush with your right edge.

With this solution, items can be reordered arbitrarily and no additional calculations need to be made.

I hope this helps!

Update:

Fiddle requiring one hack not dependent on DOM order

Here's what we changed:

CSS

.right {
        width: 30%;
        float: none;
        margin-left: 100%;
        transform: translateX(-100%);
        margin-bottom: -1.2rem; /* New */
        margin-top: calc(1.2rem + 5px); /* New */
    }
    .right:first-of-type {
        margin-top: 0;    /*optional - if there's a preceding element on the page this will prevent it from being shifted downward*/
    }

What this is doing is making the DOM think these elements have negligible space they're taking up in the document flow, when in reality we're just screwing with its margins to make it display where it was before. This shouldn't fail with an arbitrarily ordered set of elements in an arbitrarily long list. Essentially we're doing something very similar to what float does in removing the element from document flow - but we're only making it reserve space as if it had no height. Plus, it doesn't get dragged to one side of the screen or the other.

The calc(1.2rem + 5px) for margin-top is basically saying: add this margin-bottom we took away back, plus the original 5px margin we had before.

I use rem units here because you don't have any defined font-sizes. Generally, you would want to use em, and we could have here. I chose 1.2 as that's the default line-height for these items. This fix, then, would only work for one line of text in an element. You'll need to have some awareness of the height of the element being rendered in order for this to work.

Hope that helps!

Update, the second

Fiddling with minimal JavaScript

First, add this:

CSS

.right.last {
    margin-top: 0;
}

Then add this:

JavaScript

var rights = document.querySelectorAll(".right");
rights[rights.length-1].className += " last";

And you won't see that gap on the last element any longer.

Community
  • 1
  • 1
Josh Burgess
  • 9,327
  • 33
  • 46
4

I am adding one more answer here. I don't want to spoil the previous answer. However, with this answer the necessity to wrap the divs is eliminated.

JSFiddle here

   <div class="left">left one</div>
   <div class="right">right one</div>
   <div class="left">left two</div>
   <div class="right">right two</div>
   <div class="left">left three</div>
   <div class="right">right three</div>
   <div class="right">right four</div>
   <div class="right">right five</div>

Added float:left;clear : left and float:right;clear:right css properties

.left {float:left; background: coral; margin-bottom: 5px;}
.right {float:right; background: mediumaquamarine; margin-bottom: 5px;}

@media (max-width:400px){
    .left, .right  {
        width:100%;
    }
}
@media (min-width:400px){
    .left {
        float: left;
    clear : left;
    }
    .right {
        float: right;
        display: inline-block;
        margin-left: 10%;
    clear : right;
    }
}
Rama Kathare
  • 920
  • 9
  • 29
  • Wow, thanks for all your help. It's 2am here and I'm beat so I will try this in the morning. If I can get it working I'll mark as the answer. Thank you :) – Martin Hansen Lennox Nov 18 '14 at 02:01
  • Can you get rid of the extra space at the top of the right column? If so, I'll be impressed. – JakeParis Nov 18 '14 at 02:19
  • Added a dirty hack. `position:relative; top: -25px;` http://jsfiddle.net/n467un0c/13/ – Rama Kathare Nov 18 '14 at 02:25
  • These should probably be two separate `ul` elements floated to the left and right, with the item on the right coming first in the DOM. That way you can reorder the items in their lists accordingly without needing a fixed `px` value to pull elements up to where they would normally display. +1 though for heading in the right direction. – Josh Burgess Nov 18 '14 at 03:14
  • Yes, there are some limitations in achieving the result without using a wrapper. @MartinHansenLennox if you use wrappers then the solution would be more elegant. – Rama Kathare Nov 18 '14 at 03:19
  • @Shadow - It can be done without a wrapper and not needing any ordering dependent hacks. Check my response. – Josh Burgess Nov 18 '14 at 03:29
  • This almost works but adding in some more lefts and rights breaks it: http://jsfiddle.net/n467un0c/16/ – Martin Hansen Lennox Nov 18 '14 at 14:22
1

Put all the left side elements inside a separate div. Also the right elements inside a separate div.

Then make the css styling as i did in below code snippet.
It works! Hope this is what you need!

.left {float:left; background: coral; margin-bottom: 5px;}
.right {float:right; background: mediumaquamarine; margin-bottom: 5px;}
.left-wrapper{ float:left; width:60%;}
.right-wrapper{ float:right; width:30%;}

@media (max-width:400px){
    .left, .right, .left-wrapper, .right-wrapper  {
        width:100%;
    }
}
@media (min-width:400px){
    .left {
        width: 100%;
    }
    .right {
        width: 100%;
        float: none;
        display: inline-block;
        margin-left: 10%;
    }
}
<div class="left-wrapper">
<div class="left">left one</div>
<div class="left">left two</div>
<div class="left">left three</div>
</div>
<div class="right-wrapper">
<div class="right">right one</div>
<div class="right">right two</div>
<div class="right">right three</div>
<div class="right">right four</div>
<div class="right">right five</div>
</div>
vishnu
  • 624
  • 1
  • 6
  • 21
1

If you can figure out the height of the first few elements on the left side (assuming it starts with the left side) before the right side then this would be possible: http://jsfiddle.net/n467un0c/29/

.left { 
    float:none; 
    background: coral; 
    margin-bottom: 5px;
}

.right { 
    float:right; 
    background: mediumaquamarine; 
    margin-bottom: 5px;
    position: relative;
    top: -46px; /** this value should be the height of the elements added together (including the margins) on the left side BEFORE the first element on the right side... in this case it would be 'left one' and 'left two' before 'right one' **/
}

@media (max-width:400px){
    .left, .right  {
        width:100%;
    }
}
@media (min-width:400px){
    .left {
        width: 60%;
        margin-right: 10%;
    }
    .right {
        width: 30%;
        clear: right;
    }
chdltest
  • 853
  • 2
  • 6
  • 18
0

I have updated the JSFiddle here. It is working now. I have added extra wrappers to the left divs and right divs.

<div class="left">
 <div class="left">left one</div>
 <div class="left">left two</div>
</div>
<div class="right">right one</div>
<div class="left">left three</div>
<div class="right">
 <div class="right">right two</div>
 <div class="right">right three</div>
 <div class="right">right four</div>
 <div class="right">right five</div>
</div>
Rama Kathare
  • 920
  • 9
  • 29
  • Thanks for your answer. But for the fullscreen version I would like to be able to mingle .left and .right divs (rather than put them all together in a group). Sorry if that wasn't clear. Question updated. – Martin Hansen Lennox Nov 18 '14 at 01:46
  • Edited the answer. This is mingling .left and .right divs – Rama Kathare Nov 18 '14 at 01:47
  • Is there a way to do it without using wrapper divs? I just want to to be able to assign a div .left or .right and have them stack up on the correct side if full screen. If small screen, then display them all in the order given. – Martin Hansen Lennox Nov 18 '14 at 01:54