3

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.

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
Saud Kazia
  • 89
  • 1
  • 9

3 Answers3

3

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>
Asons
  • 84,923
  • 12
  • 110
  • 165
  • [code]html, body { margin: 0; } .wrapper { position: relative; } .flex { display: flex; flex-direction: column; } .flex > div { border: 1px solid; box-sizing: border-box; } @media screen and (min-width: 992px) { .flex > div:nth-child(2) { position: absolute; left: 0; top: 0; width: 30%; } .flex > div:nth-child(1), .flex > div:nth-child(3) { margin-left: 30%; } }[/code] – Saud Kazia Jun 17 '17 at 11:49
  • @SaudKazia And if you don't like positioning (depends on how it contents flows compared to the right elements), a small script will fix this and move the item in and out from the `flex` parent – Asons Jun 17 '17 at 11:51
  • this is excellent. can you please edit the question to show "how" this works so i can understand the relational behind the nth and change it in case there are more columns. this way i don't need order as well. it works both in desktop and mobile. are there any side effects? – Saud Kazia Jun 17 '17 at 12:00
  • hey sorry to bring this up but i see a big side effect on this. how do i put something below the flex box now at 100%. because of absolute positioning i cant even use clears. – Saud Kazia Jun 17 '17 at 12:51
  • @SaudKazia Don't be sorry :) ... Can you show me an example of the issue you encounter? – Asons Jun 17 '17 at 12:54
  • you can see the problem at https://www.bizbuz.in/directory/list/other/d-lights/. basically i need to "clear" the flexbox so i can add more stuff below. – Saud Kazia Jun 17 '17 at 13:07
  • don't mind the large pictures. i need to flex those also. – Saud Kazia Jun 17 '17 at 13:08
  • @SaudKazia Made a demo, changed `wrapper` class to `flex-wrapper` so it becomes more clear what it does and for what. So how does this not work? ... https://jsfiddle.net/242Lezo8/ – Asons Jun 17 '17 at 13:15
  • yes that is except the last div should not be part of the flexbox. is that possible. the reason is that there are multiple php files (wordpress) i need to make sure that the flexbox is contained in one php file and not spill over to what comes before or after otherwise i wouldn't be able to manage the contents easily.. – Saud Kazia Jun 17 '17 at 13:21
  • @SaudKazia What _last div_ should not be part of flexbox? .. in my sample only the elements inside the flex-wrapper is a part of it – Asons Jun 17 '17 at 13:24
  • i updated the fiddle to see if it indeed works. nope. when the optional has a larger height it screws the layout https://jsfiddle.net/242Lezo8/1/ – Saud Kazia Jun 17 '17 at 13:25
  • @SaudKazia Yes it does. That is the downside with this solution. Then a script is needed to fix that. I will add such solution when I come back later today, okay? – Asons Jun 17 '17 at 13:32
  • ok. i found the issue. if there is enough content in main/main2 then the flex expands but if the content in option is more then the height is of the main/main2 column – Saud Kazia Jun 17 '17 at 14:10
  • @SaudKazia Yes, I already know why this happens. And as I said, will show a script based solution later today if that is okay – Asons Jun 17 '17 at 14:12
  • hi. this works but there is a big problem with this also. before the whole page is rendered it shows a single column layout. and this is very slow. i think the best way forward was to create a script that just takes your original flex solution and set the height based on the optional column. thanks – Saud Kazia Jun 18 '17 at 06:24
  • @SaudKazia Yes, that combo would probably be faster if you have many items to toggle – Asons Jun 18 '17 at 08:34
3

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/

G-Cyrillus
  • 101,410
  • 14
  • 105
  • 129
1

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:

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701