2

I currently have a page which is comprised of boxes of content, laid out such that there are two floated columns of inequal width, and each box with a varying height that fits its content, something like this:

.box {
  padding: 1em;
  border: 1px solid black;
  margin: 3px;
}

.left.col {
  float: left;
  width: 70%;
}

.right.col {
  float: right;
  width: 30%;
}

.b1 {
  background-color: red;
  height: 150px;
}

.b2 {
  background-color: green;
  height: 80px;
}

.b3 {
  background-color: blue;
  height: 80px;
}

.b4 {
  background-color: yellow;
  height: 40px;
}

.b5 {
  background-color: purple;
  height: 30px;
}

.b6 {
  background-color: cyan;
  height: 40px;
}

.b7 {
  background-color: orange;
  height: 40px;
}
<div class="boxes">
  <div class="left col">
    <div class="box b1">1</div>
    <div class="box b2">2</div>
    <div class="box b3">3</div>
  </div>
  <div class="right col">
    <div class="box b4">4</div>
    <div class="box b5">5</div>
    <div class="box b6">6</div>
    <div class="box b7">7</div>
  </div>
</div>

I'd like to be able to responsively collapse that layout on smaller screens into a single column but in such a way that I am able to control the ordering of the content boxes. For that I'm happy to use the flexbox order with an appropriate polyfill for unsupported browsers such as flexibility. Something like this:

/* @media screen and (max-width: 570px) */
.boxes {
  display: flex;
  flex-direction: column;
}

.box {
  padding: 1em;
  border: 1px solid black;
  margin: 3px;
}

.b1 {
  background-color: red;
  height: 150px;
  order: 1
}

.b2 {
  background-color: green;
  height: 80px;
  order: 3
}

.b3 {
  background-color: blue;
  height: 80px;
  order: 5
}

.b4 {
  background-color: yellow;
  height: 40px;
  order: 2;
}

.b5 {
  background-color: purple;
  height: 30px;
  order: 4
}

.b6 {
  background-color: cyan;
  height: 40px;
  order: 6
}

.b7 {
  background-color: orange;
  height: 40px;
  order: 7
}
<div class="boxes">
  <div class="box b1">1</div>
  <div class="box b2">2</div>
  <div class="box b3">3</div>
  <div class="box b4">4</div>
  <div class="box b5">5</div>
  <div class="box b6">6</div>
  <div class="box b7">7</div>
</div>

What I haven't managed to do so far is achieve this as a single, responsive approach. I think I'd need to get rid of the <div class="col"> in order to control the box order with flex, but can't seem to achieve the same 2 column layout with flexbox and without float.

Is there a single (CSS) solution where both layouts can be achieved and switched responsively?

Edit

Both IE9 and Android 4.x account for around 2% each of my current audience of the past year, so I still need to support them. Therefore any solution using modern CSS techniques (e.g. Flexbox, CSS grid) needs to be backed up with a polyfill or gracefully fallback/degrade.

Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
DaveAlden
  • 30,083
  • 11
  • 93
  • 155
  • 1
    You won't be able to do that with flexbox because the two column layout is two dimensional and flexbox only works in one dimension. The best way to do this is with CSS grid. Examples here: https://gridbyexample.com/examples/ – pjk_ok Jan 25 '18 at 23:44

3 Answers3

2

If you can only use flexbox, then the layout is simple using nested containers or a height or max-height on the container.

The most efficient CSS solution for this layout would use Grid technology.

The solution below is complete and browser support is quite strong.

jsFiddle demo

.boxes {
  display: grid;
  grid-template-columns: 3fr 1fr;
  grid-auto-rows: 10px;
  grid-gap: 5px;
  grid-template-areas:
   " red yellow"
   " red yellow"
   " red yellow"
   " red yellow"
   " red yellow"
   " red yellow"   
   " red purple"
   " red purple"
   " red purple"
   " red purple"
   " red purple"   
   " red cyan"
   " red cyan"
   " red cyan"
   " red cyan"
   " green cyan"
   " green orange "
   " green orange "
   " green orange "
   " green orange "
   " green orange "   
   " green . " 
   " green . " 
   " green . " 
   " blue . " 
   " blue . "  
   " blue . "    
   " blue . "  
   " blue . "  
   " blue . "  
   " blue . "   
   " blue . "     
}

.b1 { grid-area: red;    background-color: red;    }
.b2 { grid-area: green;  background-color: green;  }
.b3 { grid-area: blue;   background-color: blue;   }
.b4 { grid-area: yellow; background-color: yellow; }
.b5 { grid-area: purple; background-color: purple; }
.b6 { grid-area: cyan;   background-color: cyan;   }
.b7 { grid-area: orange; background-color: orange; }

.box {
  padding: 1em;
  border: 1px solid black;
}

@media (max-width: 570px) {
  .boxes { grid-template-columns: 1fr;
           grid-template-areas: 
   " red "
   " red "
   " red "
   " red "
   " red "
   " red "
   " red "
   " red "
   " red "
   " red "
   " red "   
   " red "
   " red "
   " red "
   " red "
   " yellow "
   " yellow "
   " yellow "
   " yellow "
   " yellow "
   " yellow "
   " green "
   " green "
   " green "
   " green "
   " green "
   " green "   
   " green " 
   " green " 
   " green " 
   " purple "
   " purple "
   " purple "
   " purple "
   " purple "
   " blue " 
   " blue "  
   " blue "    
   " blue "  
   " blue "  
   " blue "  
   " blue "   
   " blue "     
   " cyan "
   " cyan "
   " cyan "
   " cyan "
   " cyan "
   " orange "
   " orange "
   " orange "
   " orange "
   " orange " ;}

}
<div class="boxes">
  <div class="box b1">1</div>
  <div class="box b2">2</div>
  <div class="box b3">3</div>
  <div class="box b4">4</div>
  <div class="box b5">5</div>
  <div class="box b6">6</div>
  <div class="box b7">7</div>
</div>

Features:

  • modern CSS3 technology
  • a container with two columns (3fr and 1fr) (more about the fr unit)
  • rows are automatically created, as needed; each row is 10px tall...
  • so a grid area that spans four rows is 40px (plus any grid-row-gap)
  • grid areas are laid out using string values in the grid-template-areas property
  • grid areas could be laid out using other methods (such as line-based placement)
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • Thanks for this. CSS grid is the dream and the future but I still have to support IE9 and Android 4.x (sorry, I should have stated browser requirements in the question - will update) so such a solution would need to be backed up with a polyfill or fallback approach. I had seen [css-grid-polyfill](https://github.com/FremyCompany/css-grid-polyfill) but it doesn't appear production-ready... – DaveAlden Jan 26 '18 at 07:46
  • Using a CSS grid polyfill would probaby kill a site on mobile anyway. If you need to support IE9, just do a mobile-first layout where the elements stack vertically, use the @supports rule on the grid-area: auto property and place your grid code inside this to prevent the older MS version of grid ruining things, and then use Grid for the larger screens. A fraction of the code & infinitely better performance that works across all browsers. I find it odd people are happy to have a vertical layout for every single person who visits the site on mobile, but not for 2% of site visitors that use IE? – pjk_ok Jan 26 '18 at 17:45
1

Nesting flexboxes seems to work but I'm not sure about debugging responsive mode in this SO embedded HTML engine. Seem like something below should do it though. Basically 3 flexboxes where the outer flexbox toggles between row and column / 100% width...

.boxes{
display:flex;

justify-content:center;
align-content:center;
align-items:center;
}

.col{
display:flex;
flex-flow: column nowrap;
justify-content:center;
align-content:center;
align-items:center;
}

.left{
flex: 0 1 auto;
min-width: 70%;
}
.right{
flex: 0 1 auto;
align-self:flex-start;
min-width: 30%;
}

.box{
flex: 0 1 auto;
min-width:100%
}


.b1{ background-color: red; height: 150px;}
.b2{ background-color: green; height: 80px;}
.b3{ background-color: blue; height: 80px;}
.b4{ background-color: yellow; height: 40px;}
.b5{ background-color: purple; height: 30px;}
.b6{ background-color: cyan; height: 40px;}
.b7{ background-color: orange; height: 40px;}

@media screen (max-width: 639px) {
.boxes{
flex-flow: column nowrap;
}
.left{
min-width: 100%;
}
.right{
min-width: 100%;
}
}
@media screen (min-width: 640px) {
.boxes{
flex-flow: row nowrap;
}
}
   
<div class="boxes">
<div class="left col">
    <div class="box b1">1</div>
    <div class="box b2">2</div>
    <div class="box b3">3</div>
</div>
<div class="right col">
    <div class="box b4">4</div>
    <div class="box b5">5</div>
    <div class="box b6">6</div>
    <div class="box b7">7</div>
</div>
</div>
Ronnie Royston
  • 16,778
  • 6
  • 77
  • 91
  • With this OP can't put e.g. nr4 between nr1 and nr2, as they show in their 2nd code sample. – Asons Jan 26 '18 at 07:46
  • I see. I'm thinking the parameters of the requirement are not well articulated which results in getting off in the weeds. The grid/box layout parameters should be well defined and simple. I'm not seeing that here. – Ronnie Royston Jan 26 '18 at 17:45
  • OP provided 2 code samples, showing the 2 layouts asked for, together with the statement _"I'd like to be able to responsively collapse that layout on smaller screens into a single column but in such a way that I am able to control the ordering of the content boxes."_, so I just wonder, how is that _not well articulated_? – Asons Jan 26 '18 at 17:50
1

Since the only pure CSS solution is CSS Grid, and given the fact that you need to support i.a. IE9, you will also need a script/fallback to accomplish this.

As Flexbox can't do this well either, dynamically (will need either fixed height or script), the absolute simplest and easiest to maintain solution, is to keep the wrappers, and just move the elements back and forth with a simple script.

Combined with that the script adds a class to the body, we can use CSS to control the elements.

Note 1; To make this more dynamic, the script can be optimized and make use of e.g. a set attribute on the element, where it should be positioned, though I didn't went that far here.

Note 2; The script toggle the layout based on orientation, though can be updated to use a width.

Note 3; The resize event make use of a throttler, which I initially found at MDN, to avoid expensive operations such as DOM modifications.

Stack snippet

(function(d, timeout) {

  function resizing() {
    if (window.innerHeight < window.innerWidth) {
      if (!(d.body.classList.contains('landscape'))) {
        d.body.classList.add('landscape');
        var target = d.querySelector('.right.col');
        target.appendChild(d.querySelector('.b4'));
        target.appendChild(d.querySelector('.b5'));
        target.appendChild(d.querySelector('.b6'));
        target.appendChild(d.querySelector('.b7'));
      }
    } else {
      if (d.body.classList.contains('landscape')) {
        d.body.classList.remove('landscape')
        var target = d.querySelector('.left.col');
        target.insertBefore(d.querySelector('.b4'), d.querySelector('.b2'))
        target.insertBefore(d.querySelector('.b5'), d.querySelector('.b3'))
        target.appendChild(d.querySelector('.b6'));
        target.appendChild(d.querySelector('.b7'));
      }
    }
  }

  /* event handlers */
  window.addEventListener("load", function() {
    resizing();
  }, false);

  window.addEventListener("resize", function() {
    if (!timeout) {
      timeout = setTimeout(function() {
        timeout = null;
        resizing();
      }, 66);
    }
  }, false);

}(document));
.box {
  padding: 1em;
  border: 1px solid black;
  margin: 3px;
}

.left.col {
  float: left;
  width: 100%;
}
.landscape .left.col {          /*  added rule  */
  width: 70%;
}

.right.col {
  float: right;
  width: 30%;
}

.b1 {
  background-color: red;
  height: 150px;
}

.b2 {
  background-color: green;
  height: 80px;
}

.b3 {
  background-color: blue;
  height: 80px;
}

.b4 {
  background-color: yellow;
  height: 40px;
}

.b5 {
  background-color: purple;
  height: 30px;
}

.b6 {
  background-color: cyan;
  height: 40px;
}

.b7 {
  background-color: orange;
  height: 40px;
}
<div class="boxes">
  <div class="left col">
    <div class="box b1">1</div>
    <div class="box b2">2</div>
    <div class="box b3">3</div>
  </div>
  <div class="right col">
    <div class="box b4">4</div>
    <div class="box b5">5</div>
    <div class="box b6">6</div>
    <div class="box b7">7</div>
  </div>
</div>
Asons
  • 84,923
  • 12
  • 110
  • 165
  • 1
    Neat. Your solution solves both the ordering and column collapsing elegantly. The use of JS is of course inevitable with the need to support IE9 and as you say it can be made more generic e.g. using `data-order` attribute to specify index for dynamic re-ordering. – DaveAlden Jan 26 '18 at 09:25