【问题标题】:Flexbox: Reverse Direction on Wrap / "Snake Wrap"Flexbox: 反向包装/“蛇形包装”
【发布时间】:2020-04-16 07:12:19
【问题描述】:

我想找到一种方法,让单个容器中的元素换行到与前一行相反方向的新行,就像一条蛇在自己身上弯曲一样。我无法使用 flexbox 以及 flex-direction 和 flex-wrap 属性的任何组合来实现此结果。

flexbox 结果与期望结果的图像比较:

这是一个 sn-p 显示 flexbox 结果与期望结果(伪造):

body {
  font: 400 14px 'Arial', sans-serif;
}

.title {
  font-size: 1em;
  padding: 20px 0;
}

.title:first-child {
  padding: 0 0 20px;
}

.flex-container, .fake-container, .fake-row {
  width: 500px;
  background: orange;
  display: flex;
}

.flex-container {
  flex-flow: row wrap; /* any combination of row/reverse & wrap/reverse */
}

.item {
  display: inline-block;
  background: purple;
  width: 80px;
  max-width: 80px;
  height: 80px;
  margin: 10px;
  line-height: 80px;
  color: white;
  font-weight: bold;
  font-size: 2em;
  text-align: center;
}

.fake-container {
  flex-flow: column nowrap;
}

.fake-row {
  flex-flow: row wrap;
  height: 100px;
}

.fake-row:nth-child(2) {
  flex-flow: row-reverse wrap;
}
<div class="title">Flexbox result:</div>
<div class="flex-container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
  <div class="item">4</div>
  <div class="item">5</div>
  <div class="item">6</div>
  <div class="item">7</div>
</div>

<div class="title">Desired result (faked):</div>
<div class="fake-container">
  <div class="fake-row">
    <div class="item">1</div>
    <div class="item">2</div>
    <div class="item">3</div>
    <div class="item">4</div>
    <div class="item">5</div>
  </div>
  <div class="fake-row">
    <div class="item">6</div>
    <div class="item">7</div>
  </div>
</div>

尽管我很想找到一个纯 CSS 解决方案来实现这种“蛇形包装”,但我开始怀疑它的可能性。我确信可以使用 javascript 解决方案,但不确定如何在不使事情过于复杂的情况下继续创建一个。

有什么简单的解决方案吗?感谢您的宝贵时间。

【问题讨论】:

  • 你不能用 flexbox 做到这一点。 Flexbox 只是一维的。您可以换行到下一行,但它与上一行相同。
  • 欢迎来到 SO @Vern,我不知道真正的答案,但请通过 css grids 试试这个

标签: html css flexbox


【解决方案1】:

一个 CSS 网格解决方案 similar to this one,它适用于每行固定数量的元素(也就是固定数量的列)。

.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>

如果你想要一个通用的解决方案,这里是一个使用 JS 和 flexbox 的解决方案。诀窍是根据每个项目的行位置设置order 属性。我将依靠this old answer 进行大部分计算,以便找到行数/列数:

//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>

在某些情况下,当最后一行没有填满项目时,它会出现小问题。您可以通过为最后一个元素添加一些边距来纠正此问题

//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>

您只需将if(i%2!=1)更改为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>

【讨论】:

  • 这是一个非常聪明和优雅的解决方案。我不知道 CSS 网格,现在肯定想了解更多关于它们的信息。不幸的是,就我而言,由于我是根据元素的宽度和屏幕的宽度进行换行,所以我不能使用固定列。尽管如此,还是感谢这个想法!
  • @Vern 添加了一个 JS 解决方案,将更新更多细节
  • 哇,这太棒了!我会花一点时间阅读 JS 并尝试理解这里发生的一切,但它完美地工作并满足所有要求。因为我怀疑那里(还)有一个真正的纯 CSS 解决方案,所以我将其标记为已接受的答案。非常感谢!
猜你喜欢
  • 1970-01-01
  • 2012-09-27
  • 1970-01-01
  • 1970-01-01
  • 2021-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-31
相关资源
最近更新 更多