Reputation: 89
I am trying to layout multiple columns in the following way. I am using Wordpress but editing the PHP, HTML, CSS etc.
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
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>
Here's how it works:
grid-template-columns
establishes the number of columns. Same concept for grid-template-rows
.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.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
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
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
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