1

On a mobile layout, I have six divs one below another in order of importance. On desktop, I would like to reorder the items. On the image below, the mobile layout is on the left, the desktop layout on the right.

Mobile and desktop layout

The heights of the six items are not related to each other. The layout should work with any heights.

I tried different solutions. The easiest to achieve the desktop layout would be to create two wrapper divs. But this does not allow to have a different order on mobile.

I tried to adapt forced horizontal wrapping in flexbox to vertical. This answer for column breaks almost worked, but it needs a fixed height for the container.

I searched about girds, and unfortunately, it seems impossible to have more than one element in a grid area.

Is there a way to solve this problem, without using Javascript ? (Our current solution involves Javascript, but the visitor can notice the rearrangement of the divs).

Lorenz Meyer
  • 19,166
  • 22
  • 75
  • 121
  • would this be an hint for you ? a mix of flex/order and column CSS https://stackoverflow.com/questions/59398922/bootstrap-order-changes-depending-if-displayed-as-1-or-2-column/59401605#59401605 – G-Cyrillus Dec 19 '19 at 14:07
  • @G-Cyr Thanks. I already looked at such a solution. There are two missing pieces in the answer you linked. 1. The two columns do not have the same width (solution positive and negative margin) 2. The break MUST be after the 3rd div. I didn't find a way to force it. `break-before` and `break-after` do not seem to work. – Lorenz Meyer Dec 19 '19 at 14:39
  • Unfortunately this is a classic masonry issue. Neither flexbox or css-grid offer complete solutions without compromises. – Paulie_D Dec 19 '19 at 15:10
  • Use column wrappers and display:contents of course but support for that is not ideal. – Paulie_D Dec 19 '19 at 15:12
  • 1
    https://stackoverflow.com/questions/34480760/is-it-possible-for-flex-items-to-align-tightly-to-the-items-above-them – Paulie_D Dec 19 '19 at 15:14

3 Answers3

2
  • flex requires an height to wrap columns, (see last snippet demo using javascript to set the height required)

  • grid requires to set spanning to inbricate elements,

  • column CSS do not understand order ...

... Maybe there is float left over here

// set a random min-height on children 
for(let e of document.getElementsByClassName('element')) {
  e.style.minHeight = Math.floor(25 + Math.random() * 72) + 'px';
}
* {
  margin: 0;
  box-sizing: border-box;
}

/* reset breakpoint to your own value */
@media screen and (min-width: 568px) {
  div {
    width: 50%;
  }
  div:nth-child(odd) {
    float: left;
    clear: left;
  }
  div:nth-child(even) {
    overflow: hidden;
  }
}

/* demo purpose */
section {
  counter-reset: div;
}
div:before {
  counter-increment: div;
  content: "Div N°"counter(div);
}
div {
  border: solid;
  display:flex;
  align-items:center;
  justify-content:center;
}
<section>
  <div class="element"></div>
  <div class="element"></div>
  <div class="element"></div>
  <div class="element"></div>
  <div class="element"></div>
  <div class="element"></div>
  <div class="element"></div>
</section>

Even then , gaps appears in between elements, float always breaks a line when following a non floatting element.


The masonry script could be an option, or javascript to sort out the height , so flex column could be used aside order.

Here is the javascript idea:

hl = 0;
hr = 0;
nb = 0;
for (let e of document.getElementsByClassName("element")) {
  nb++;
  h = Math.floor(50 + Math.random() * 50);
  e.style.height = h + "px";
  if (nb % 2 === 0) {
    hr = hr + h;
  } else {
    hl = hl + h;
  }
  if (hl > hr) {
    document.getElementById("test").style.height = hl + "px";
  } else {
    document.getElementById("test").style.height = hr + "px";
  }
}
* {
  margin:0;
  box-sizing:border-box;
}
div {border:solid;box-sizing:border-box;width:25%;background:tomato;}
/* demo purpose */
section {
  counter-reset: div;
  background:gray;
  display:flex;
  flex-direction:column ;
  flex-wrap:wrap;
}
div:nth-child(even) {
  order:2;
  width:75%;
  background:orange
}
div:before {
  display:block;
  counter-increment: div;
  content: "Div N°"counter(div);
}
@media screen and (max-width:568px) {
  section {
    height:auto!important;/* because of js script */
    display:block;
  }
  section div:nth-child(1n) {width:auto;}
}
<section id="test">
  <div class="element"></div>
  <div class="element"></div>
  <div class="element"></div>
  <div class="element"></div>
  <div class="element"></div>
  <div class="element"></div>
</section>
G-Cyrillus
  • 101,410
  • 14
  • 105
  • 129
  • 1
    omg I start to appreciate a lot more flexbox and grids after seeing theses hacks, old days were very hard I can imagine. – Wilson Dec 19 '19 at 14:42
  • This is the best solution so far, but the demo shows it's weekness. – Lorenz Meyer Dec 19 '19 at 14:58
  • @LorenzMeyer here is the javascript idea with flex column and order to draw the columns https://codepen.io/gc-nomade/pen/WNbprXj in real, you will need to rtrieve offset height of odds/even children to claculate height value to set and probably deal with margins if any in between children. – G-Cyrillus Dec 19 '19 at 15:21
  • @Wilson with a bit of js to retrieve the height needed for wrapping, flex is the most appropriate i believe . I added a snippet if you are curious, or here the codepen to play with : https://codepen.io/gc-nomade/pen/WNbprXj ;) – G-Cyrillus Dec 19 '19 at 15:27
2

@Paulie_D's comment and @G-Cyr's answer inspired me this solution:

The six divs that must be aligned are grouped in two intermediate <div>, representing the two columns in the desktop layout. They are grand-children of the container, instead of children.

On mobile, we ignore the column divs with display: contents; and reorder the grand-children using flexbox's order: n;. On desktop we display the two columns using display: inline-block;, and leave the grand-children in source-order.

for(let e of document.getElementsByClassName('item')) {
  e.style.minHeight = Math.floor(25 + Math.random() * 72) + 'px';
}
.container {
  display: flex;
  flex-direction: column;
}
.item {
  margin-bottom: 0.5em;
  text-align: center;
  background:#ddd;
  padding: 1em;
}
.left, .right{
  display: contents;
}
.item1, .item2{
  order: 1;
}
.item3, .item4{
  order: 2;
}
.item5, .item6{
  order: 3;
}

@media (min-width: 40em){
  .left, .right{
    display: inline-block;
    vertical-align: top;
  }
  .container{
    display: block;
  }
  .left{
    width: calc(67% - 0.5em);
    margin-right: 0.5em;
  }
  .right{
    width: 33%;
  }
}
<div class="container">
  <div class="left">
    <div class="item item1">1</div>
    <div class="item item3">3</div>
    <div class="item item5">5</div>
  </div><div class="right">
    <div class="item item2">2</div>
    <div class="item item4">4</div>
    <div class="item item6">6</div>
  </div>
</div>
Lorenz Meyer
  • 19,166
  • 22
  • 75
  • 121
1

RESULT

enter image description here

I'v used CSS Grids for this problem.

HTML

<div class="list">
  <div class="item one">1</div>
  <div class="item two">2</div>
  <div class="item three">3</div>
  <div class="item four">4</div>
  <div class="item five">5</div>
  <div class="item six">6</div>
</div>

CSS

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: 'Verdana';
    color: #fff;
}


.list {
  background: gold;
  display: grid;
  grid-gap: 10px;
  text-align: center;

  grid-template-columns: repeat(1, 1fr);
  grid-auto-rows: minmax(70px, auto);
  grid-template-areas: 
        "one"
        "two"
        "three"
        "three"
        "four"
        "five"
        "six";

}

.item {
  background: gray
}

.one {
    grid-area: one;
}

.two {
    grid-area: two;
}

.three {
    grid-area: three;
}

.four {
    grid-area: four;
}

.five {
    grid-area: five;
}

.six {
    grid-area: six;
}


@media screen and (min-width: 650px) {
    .list {
      grid-template-columns: repeat(2, 1fr);
      grid-template-areas: 
        "one   two"
        "one   two"
        "one   four"
        "three four"
        "three six"
        "three six"
        "three six"
        "five  six"
        "five  .";
    }
}
Wilson
  • 9,006
  • 3
  • 42
  • 46