Flexbox: Reverse Direction on Wrap / “Snake Wrap”

后端 未结 1 1620
萌比男神i
萌比男神i 2020-12-03 18:14

I would like to find a way to have elements in a single container wrap to a new line going the opposite direction of the previous line, like a snake curving back on itself.

相关标签:
1条回答
  • 2020-12-03 18:48

    A CSS grid solution similar to this one that works with a fixed number of elements per row (aka fixed number of columns).

    .flex-container {
      width: 500px;
      background: orange;
      display: grid;
      grid-template-columns:repeat(5,1fr); /*define the number of column*/
      grid-auto-flow:dense; /* this is important to fill all the space*/
      grid-gap:20px;
      padding:10px;
    }
    
    .item {
      background: purple;
      height: 80px;
      line-height: 80px;
      color: white;
      font-weight: bold;
      font-size: 2em;
      text-align: center;
    }
    
    .item:nth-child(10n + 6)  {grid-column:5}
    .item:nth-child(10n + 7)  {grid-column:4}
    .item:nth-child(10n + 8)  {grid-column:3}
    .item:nth-child(10n + 9)  {grid-column:2}
    /*.item:nth-child(10n + 10) {grid-column:1} not needed*/
    /* For N = number of columns 
      .item:nth-child((2xN)n + (N+1)) { grid-column:N; }
      .item:nth-child((2xN)n + (N+2)) { grid-column:(N-1); }
      ....
      .item:nth-child((2xN)n + (2xN)) { grid-column:1; }
    
    */
    
    .item:before {
      content:counter(num);
      counter-increment:num;
    }
    body {
      font: 400 14px 'Arial', sans-serif;
      counter-reset:num;
    }
    <div class="flex-container">
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
    </div>

    If you want a generic solution here is one using JS and flexbox. The trick is to set the order property based on the row position of each item. I will rely on this old answer to do most of the calculation in order to find the number of rows/columns:

    //total number of element
    var elems = document.querySelectorAll('.item');
    var n_t = elems.length;
    //width of an element
    var w = parseInt(document.querySelector('.item').offsetWidth);
    //full width of element with margin
    var m = document.querySelector('.item').currentStyle || window.getComputedStyle(document.querySelector('.item'));
    w = w + parseInt(m.marginLeft) + parseInt(m.marginRight);
    //width of container
    var w_c = parseInt(document.querySelector('.flex-container').offsetWidth);
    //padding of container
    var c = document.querySelector('.flex-container').currentStyle || window.getComputedStyle(document.querySelector('.flex-container'));
    var p_c = parseInt(c.paddingLeft) + parseInt(c.paddingRight);
    
    
    var adjust = function(){
       //only the width of container will change
       w_c = parseInt(document.querySelector('.flex-container').offsetWidth);
       //Number of columns
       nb = Math.min(parseInt((w_c - p_c) / w),n_t);
       //Number of rows
       nc = Math.ceil(n_t/nb);
       for(var j = 0;j<nb;j++) {
         for(var i = 0;i<nc;i++) {
           if(j + i*nb >= n_t) /* we exit if we reach the number of elements*/
            break
           if(i%2!=1) 
             elems[j + i*nb].style.order=j + i*nb; /* normal flow */
           else
             elems[j + i*nb].style.order=(nb - j) + i*nb; /* opposite flow */
         }
        }
    }
    
    adjust()
    window.addEventListener('resize', function(){adjust()})
    .flex-container {
      background: orange;
      display: flex;
      flex-wrap:wrap;
    }
    
    .item {
      background: purple;
      height: 80px;
      width:80px;
      margin:10px;
      line-height: 80px;
      grid-gap:20px;
      color: white;
      font-weight: bold;
      font-size: 2em;
      text-align: center;
    }
    
    
    .item:before {
      content:counter(num);
      counter-increment:num;
    }
    body {
      font: 400 14px 'Arial', sans-serif;
      counter-reset:num;
    }
    <div class="flex-container">
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
    </div>

    There is a little issue with the last line in some cases when it's not full of item. You can rectify this by adding some margin to the last element

    //total number of element
    var elems = document.querySelectorAll('.item');
    var n_t = elems.length;
    //width of an element
    var w = parseInt(document.querySelector('.item').offsetWidth);
    //full width of element with margin
    var m = document.querySelector('.item').currentStyle || window.getComputedStyle(document.querySelector('.item'));
    w = w + parseInt(m.marginLeft) + parseInt(m.marginRight);
    //width of container
    var w_c = parseInt(document.querySelector('.flex-container').offsetWidth);
    //padding of container
    var c = document.querySelector('.flex-container').currentStyle || window.getComputedStyle(document.querySelector('.flex-container'));
    var p_c = parseInt(c.paddingLeft) + parseInt(c.paddingRight);
    
    
    var adjust = function(){
       //only the width of container will change
       w_c = parseInt(document.querySelector('.flex-container').offsetWidth);
       //Number of columns
       nb = Math.min(parseInt((w_c - p_c) / w),n_t);
       //Number of rows
       nc = Math.ceil(n_t/nb);
       for(var j = 0;j<nb;j++) {
         for(var i = 0;i<nc;i++) {
           if(j + i*nb >= n_t) /* we exit if we reach the number of elements*/
            break
           elems[j + i*nb].style.marginLeft='10px'; /*we rest the margin*/
           if(i%2!=1) 
             elems[j + i*nb].style.order=j + i*nb; /* normal flow */
           else {
             elems[j + i*nb].style.order=(nb - j) + i*nb; /* opposite flow */
             /*margin fix*/
             if(i == (nc - 1) && (j + i*nb == (n_t - 1)) && j<(nb-1)) {
                elems[j + i*nb].style.marginLeft=((nb*nc - n_t)*w + 10) + 'px';
             }
           }
         }
        }
    }
    
    adjust()
    window.addEventListener('resize', function(){adjust()})
    .flex-container {
      background: orange;
      display: flex;
      flex-wrap:wrap;
    }
    
    .item {
      background: purple;
      height: 80px;
      width:80px;
      margin:10px;
      line-height: 80px;
      grid-gap:20px;
      color: white;
      font-weight: bold;
      font-size: 2em;
      text-align: center;
    }
    
    
    .item:before {
      content:counter(num);
      counter-increment:num;
    }
    body {
      font: 400 14px 'Arial', sans-serif;
      counter-reset:num;
    }
    <div class="flex-container">
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
    </div>


    You can easily switch to the other direction by simply changing if(i%2!=1) to if(i%2!=0)

    //total number of element
    var elems = document.querySelectorAll('.item');
    var n_t = elems.length;
    //width of an element
    var w = parseInt(document.querySelector('.item').offsetWidth);
    //full width of element with margin
    var m = document.querySelector('.item').currentStyle || window.getComputedStyle(document.querySelector('.item'));
    w = w + parseInt(m.marginLeft) + parseInt(m.marginRight);
    //width of container
    var w_c = parseInt(document.querySelector('.flex-container').offsetWidth);
    //padding of container
    var c = document.querySelector('.flex-container').currentStyle || window.getComputedStyle(document.querySelector('.flex-container'));
    var p_c = parseInt(c.paddingLeft) + parseInt(c.paddingRight);
    
    
    var adjust = function(){
       //only the width of container will change
       w_c = parseInt(document.querySelector('.flex-container').offsetWidth);
       //Number of columns
       nb = Math.min(parseInt((w_c - p_c) / w),n_t);
       //Number of rows
       nc = Math.ceil(n_t/nb);
       for(var j = 0;j<nb;j++) {
         for(var i = 0;i<nc;i++) {
           if(j + i*nb >= n_t) /* we exit if we reach the number of elements*/
            break
           elems[j + i*nb].style.marginLeft='10px'; /*we rest the margin*/
           if(i%2!=0) 
             elems[j + i*nb].style.order=j + i*nb; /* normal flow */
           else {
             elems[j + i*nb].style.order=(nb - j) + i*nb; /* opposite flow */
             /*margin fix*/
             if(i == (nc - 1) && (j + i*nb == (n_t - 1)) && j<(nb-1)) {
                elems[j + i*nb].style.marginLeft=((nb*nc - n_t)*w + 10) + 'px';
             }
           }
         }
        }
    }
    
    adjust()
    window.addEventListener('resize', function(){adjust()})
    .flex-container {
      background: orange;
      display: flex;
      flex-wrap:wrap;
    }
    
    .item {
      background: purple;
      height: 80px;
      width:80px;
      margin:10px;
      line-height: 80px;
      grid-gap:20px;
      color: white;
      font-weight: bold;
      font-size: 2em;
      text-align: center;
    }
    
    
    .item:before {
      content:counter(num);
      counter-increment:num;
    }
    body {
      font: 400 14px 'Arial', sans-serif;
      counter-reset:num;
    }
    <div class="flex-container">
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
    </div>

    0 讨论(0)
提交回复
热议问题