Saud Kazia
Saud Kazia

Reputation: 89

How to use flexbox to layout multiple columns?

I am trying to layout multiple columns in the following way. I am using Wordpress but editing the PHP, HTML, CSS etc. site layout

Here is the HTML. Imagine A B C as blocks of code without fixed height and percentage width as shown.

<div class="flex wrap">
     <div class="main">A</div>
     <div class="optional">B</div>
     <div class="main2">C</div>
</div>

As long as I am using flexbox I can play around with view ports to change order of flex. But right now I am not able to get the desired desktop layout.

I can use columns (bootstrap) with nested divs for 2nd column to create the desktop layout, but then the mobile layout will then be BAC or ACB not ABC.

Here is my CSS:

.flex.wrap {
    flex-wrap: wrap;
}

.flex {
    display: flex!important; /* to override display:block */
}

.main {
    order: 2;
    flex: 0 75%;
}

.optional {
    order: 1;
    flex: 0 25%;
}
.main2 {
    order: 3;
    flex: 0 75%
}

I did my research and have seen many such questions being asked but without any satisfactory answer that suits my needs.

I don't have to use flex box and I don't need a pure css solution either - as long as it can work on most modern browsers and mobile. I just need something that works. So I am open to any suggestion.

I could also use display: none on two blocks of code but that will make the code add 67 lines more which I think should not be necessary.

Even if there was a php solution that may work like below it might work.

If (code that determines mobile viewport) echo html
else
echo nothing

And another same code for where I want the desktop version to be. I know this is not elegant but at the end I care for the result including performance rather than anything else.

Any help will be greatly appreciated.

Upvotes: 3

Views: 7963

Answers (3)

Michael Benjamin
Michael Benjamin

Reputation: 371221

The most efficient way to achieve this layout is with CSS Grid Layout:

.container {
  display: grid;
  grid-template-columns: 25% 1fr;
  grid-template-rows: 1fr 1fr 1fr;
  grid-gap: 5px;
  grid-template-areas: " B A " 
                       " B C ";
}

.A { grid-area: A; }
.B { grid-area: B; }
.C { grid-area: C; }

@media ( max-width: 800px ) {
  .container {
    grid-template-columns: 1fr;
    grid-template-areas: "A" "B" "C";
  }
}

/* non-essential styles; for demo only */
.container { height: 200px; }
.A { background-color: aqua; }
.B { background-color: gold; }
.C { background-color: lightgreen; }
.container > div { display: flex; align-items: center; justify-content: center; }
<div class="container">
  <div class="A">A</div>
  <div class="B">B</div>
  <div class="C">C</div>
</div>

jsFiddle demo


Here's how it works:

  • The number of values in grid-template-columns establishes the number of columns. Same concept for grid-template-rows.
  • The grid-template-areas property allows you to arrange your layout using ASCII visual art. Place the grid area names (which are defined for each element) in the position where you want them to appear.
  • The fr unit tells a column / row to consume available space. It is similar to flex-grow.

Also, there are no changes to the original HTML structure. It's kept as simple as possible. And there is no need to use the order property.


Browser Support for CSS Grid

  • Chrome - full support as of March 8, 2017 (version 57)
  • Firefox - full support as of March 6, 2017 (version 52)
  • Safari - full support as of March 26, 2017 (version 10.1)
  • Edge - full support as of October 16, 2017 (version 16)
  • IE11 - no support for current spec; supports obsolete version

Here's the complete picture: http://caniuse.com/#search=grid


The Problem with Flexbox

The desktop layout you want can be achieved with flexbox if you can set a fixed height on the container.

Using flex-flow: column wrap, this will allow one item to consume all space in the first column. The second and third items can then wrap and share the space in the second column. You can use the order property to re-arrange the position of each item.

But, as mentioned, a fixed height on the container is necessary, because otherwise the items have no breakpoint and will not wrap.

In row-direction, the desktop layout is not possible with flexbox because you're asking an item to wrap under another item in the same row. In other words, "B" establishes the height of the row, then you want "C" to wrap under "A" in that row. That's not how flexbox works.

Here's a more complete explanation:

Upvotes: 1

Asons
Asons

Reputation: 87191

Since you can't set fixed heights, Flexbox can't do that alone, so either you combine it with positioning or script, or use CSS Grid (it has less browser support than Flexbox though).

Here is a sample using positioning, and updated with the classes from your original code sample to make it easier to follow.

html, body {
  margin: 0;
}
.wrapper {
  position: relative;
}
.flex {
  display: flex;
  flex-direction: column;
}
.flex > div {
  border: 1px solid;
  box-sizing: border-box;
}

/*  for desktop  */
@media screen and (min-width: 600px) {
  .flex > .optional {
    position: absolute;
    left: 0;
    top: 0;
    width: 25%;
    height: 100%;
  }
  .flex .main,
  .flex .main2 {
    margin-left: 25%;    /*  match the width of the "optinal"  */
  }
}
<div class="wrapper">
  <div class="flex">
    <div class="main">A</div>
    <div class="optional">B</div>
    <div class="main2">C</div>
  </div>
</div>


Updated

In some situations one can simply cannot use absolute positioning, for example when both the left and right columns (in desktop mode) could grow beyond the other.

Here is a version using a small script, which mimics a media query and move the optional element in and out of the flex container.

It also toggle a class on the body, in this case mobileview, which is being used in the CSS to toggle wrapper as a flex container and set appropriate properties on the optional when it is being a flex item child to the wrapper instead of the flex.

With the minwidth = 600 variable in the script one controls at which width the layout should toggle.

Here is a fiddle demo to play with as well

(function(d, w, timeout) {

  /* custom variables */
  var flexcontainer = '.flex',
    flexitem = '.optional',
    minwidth = 600,             /* if null, then when viewport is portrait */
    classname = 'mobileview';
    
  /* here happens the magic */
  function resizeing() {
    if ((minwidth && (minwidth < w.innerWidth)) ||
        (!minwidth && (w.innerHeight < w.innerWidth))) {
      if (!(d.body.classList.contains(classname))) {
        /* move it outside the main flexcontainer */
        d.body.classList.add(classname);
        var fca = qSA(flexcontainer);
        for (var i = 0; i < fca.length; i++) {
          fca[i].parentNode.appendChild(qS(flexitem, fca[i]))
        }
      }
    } else {
      if (d.body.classList.contains(classname)) {      
        /* move it back inside the main flexcontainer */
        d.body.classList.remove(classname)
        var fca = qSA(flexcontainer);
        for (var i = 0; i < fca.length; i++) {
          fca[i].appendChild(qS(flexitem, fca[i].parentNode))
        }
      }      
    }
  }
  
  /* run at page load init resize */
  w.addEventListener("load", function() {
    resizeing();
  }, false);
  
  /* grab when viewport resize */
  w.addEventListener("resize", function() {
    if (!timeout) {
      timeout = setTimeout(function() {
        timeout = null;
        resizeing();
      }, 66);
    }
  }, false);
  
  /* helper variables */
  var qSA = function(s, el) {
      return (el) ? el.querySelectorAll(s) :
        d.querySelectorAll(s)
    },
    qS = function(s, el) {
      return (el) ? el.querySelector(s) :
        d.querySelector(s)
    };
}(document, window));
html, body {
  margin: 0;
}
.wrapper .flex {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
}
.wrapper .flex > div {
  flex-grow: 1;
  border: 1px solid;
  box-sizing: border-box;
}
.wrapper .flex .optional {
  order: 1;
}
.wrapper .flex .main2 {
  order: 2;
}

/*  for desktop  */
.mobileview .wrapper {
  display: flex;
}
.mobileview .wrapper .optional {
  flex-basis: 25%;
  order: -1;
  border: 1px solid;
  box-sizing: border-box;
}
<div class="wrapper">
  <div class="flex">
    <div class="main">A</div>
    <div class="optional">
      B as a lot more content<br>
      B as a lot more content<br>
      B as a lot more content<br>
      B as a lot more content<br>
      B as a lot more content<br>
    </div>
    <div class="main2">C</div>
  </div>
</div>

<h5>More than one container and other text etc.</h5>

<div class="wrapper">
  <div class="flex">
    <div class="main">
      A as a lot more content<br>
      A as a lot more content<br>
      A as a lot more content<br>
      A as a lot more content<br>
      A as a lot more content<br>
    </div>
    <div class="optional">B</div>
    <div class="main2">C</div>
  </div>
</div>

Upvotes: 4

G-Cyrillus
G-Cyrillus

Reputation: 105893

More for info than an efficient answer at this time.

You may switch from flex to grid, where

  • grid allows to set rows & columns, ordering elements that can also span,
  • flex allows reordering :

Example

body {
  display: grid;
  grid-template-columns: 25% 1fr;
  grid-gap: 10px
}

.opt {
  grid-column: 1;
  grid-row: 1 / span 2;
  background: gold
}

div {
  background: turquoise
}

@media all and (max-width: 360px) {/* set here width where you need to switch layout */
  body {
    display: flex;
    flex-flow: column
  }
  .a {
    order: -2
  }
  .opt {
    order: -1
  }
  div {
    margin: 5px 10px
  }
}
<div class="a">A of any height</div>
<div class="c">C of any height</div>
<div class="opt">B of any height</div>

If grid looks interresting to you : https://css-tricks.com/snippets/css/complete-guide-grid/

Upvotes: 4

Related Questions