当flexbox项目以列模式换行时,容器不会增加其宽度

时间:2015-11-24 10:55:00

标签: html css css3 flexbox

我正在研究一个嵌套的flexbox布局,它应该按如下方式工作:

最外层(ul#main)是一个水平列表,当添加更多项目时,它必须向右展开。如果它变得太大,应该有一个水平滚动条。

#main {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    overflow-x: auto;
    /* ...and more... */
}

此列表中的每个项目(ul#main > li)都有一个标题(ul#main > li > h2)和一个内部列表(ul#main > li > ul.tasks)。此内部列表是垂直的,应在需要时包装到列中。当包装到更多列时,其宽度应该增加,以便为更多项目腾出空间。此宽度增加也应适用于外部列表的包含项。

.tasks {
    flex-direction: column;
    flex-wrap: wrap;
    /* ...and more... */
}

我的问题是当窗口的高度太小时内部列表不会换行。我尝试过对所有flex属性进行大量篡改,试图在CSS-Tricks精心遵循指导原则,但没有运气。

JSFiddle显示了我到目前为止所拥有的内容。

预期结果 (我想要的)

My desired output

实际结果 (我得到的)

My current output

较旧的结果 (我在2015年得到的)

My older output

更新

经过一番调查后,这开始变得更加严重。 所有主流浏览器的行为方式相同,并且与我的嵌套Flexbox设计无关。更简单的flexbox列布局拒绝在项目换行时增加列表的宽度。

other JSFiddle清楚地证明了这个问题。在当前版本的Chrome,Firefox和IE11中,所有项目都正确包装;列表的高度在row模式下增加,但其宽度在column模式下不会增加。此外,在更改column模式的高度时,根本没有立即回流元素,但存在row模式。

然而,official specs (look specifically at example 5)似乎表明我想做的事情应该是可能的。

有人可以针对这个问题找到解决方法吗?

更新2

在调整大小事件期间使用JavaScript进行大量实验来更新各种元素的高度和宽度之后,我得出的结论是,尝试以这种方式解决它太复杂和太麻烦了。此外,添加JavaScript肯定会破坏flexbox模型,该模型应尽可能保持干净。

现在,我回到overflow-y: auto而不是flex-wrap: wrap,以便内部容器在需要时垂直滚动。它并不漂亮,但它是一种前进的方式,至少不会过多地破坏可用性。

8 个答案:

答案 0 :(得分:133)

问题

这似乎是弹性布局的一个根本缺陷。

列方向的Flex容器不会展开以容纳其他列。 (这不是flex-direction: row中的问题。)

这个问题已被多次询问(参见下面的列表),CSS中没有干净的答案。

很难将此作为错误,因为问题出现在所有主流浏览器中。但确实提出了这个问题:

  

所有主流浏览器如何才能获得Flex容器   在行方向上展开但不在列方向上展开?

你会认为其中至少有一个会做对。我只能推测原因。也许这是一个技术上很难的实现,并且被搁置了这个迭代。

更新:此问题似乎已在Edge v16中解决。

问题的说明

OP创建了一个有用的演示来说明问题。我在这里复制它:http://jsfiddle.net/nwccdwLw/1/

解决方法选项

Stack Overflow社区的Hacky解决方案:

更多分析

描述相同问题的其他帖子

答案 1 :(得分:5)

晚些时候参加聚会,但几年后仍然遇到此问题。最终找到了使用网格的解决方案。在容器上可以使用

display: grid;
grid-auto-flow: column;
grid-template-rows: repeat(6, auto);

我在CodePen上有一个示例,可以在flexbox问题和网格修复之间切换:https://codepen.io/MandeeD/pen/JVLdLd

答案 2 :(得分:4)

我刚刚在这里找到了一个非常棒的PURE CSS解决方法。

https://jsfiddle.net/gcob492x/3/

棘手的问题:在列表div中设置writing-mode: vertical-lr,然后在列表项中设置writing-mode: horizontal-tb。我必须在JSFiddle中调整样式(删除很多对齐样式,对于解决方案而言这不是必需的。)

注意:评论说它仅在基于Chromium的浏览器中有效,而在Firefox中不可用。我只在Chrome中进行过个人测试。可能有一种方法可以对其进行修改以使其在其他浏览器中运行,或者对上述浏览器进行了更新,以使其能够正常工作。

对此留言大喊:When flexbox items wrap in column mode, container does not grow its width。深入研究该问题线程使我进入https://bugs.chromium.org/p/chromium/issues/detail?id=507397#c39,这使我进入了这个JSFiddle。

答案 3 :(得分:1)

不幸的是,许多主要的浏览器都在多年后遭受此bug的困扰。考虑使用Javascript解决方法。每当浏览器窗口调整大小或将内容添加到元素时,请执行此代码以将其调整为适当的宽度。您可以在框架中定义一个指令来为您完成。

    element.style.flexBasis = "auto";
    element.style.flexBasis = `${element.scrollWidth}px`;

答案 4 :(得分:0)

我以这种方式解决了我的问题,希望它可以帮助其他人偶然发现:

  _ensureWidth(parentElement) {
let totalRealWidth = 0;
let parentElementBoundingRect  = parentElement.getBoundingClientRect()
let lowestLeft = parentElementBoundingRect.x;
let highestRight = parentElementBoundingRect.width;
for (let i = 0; i < parentElement.children.length; i++) {
  let { x, width } = parentElement.children[i].getBoundingClientRect();
  if (x < lowestLeft) {
    lowestLeft = x;
  }
  if (x + width > highestRight) {
    highestRight = x + width;
  }
}
totalRealWidth = highestRight - lowestLeft;
parentElement.style.width = `${totalRealWidth}px`;

}

答案 5 :(得分:0)

解决方法:

在元素加载到屏幕上后,使用 javascript 不难手动设置包装器的宽度。宽度始终是最后一个子元素的右手点。

在 React 中,我根据添加到 flex 包装器的任何子项更新了布局更改,但这可以在您向包装器添加或删除子项的任何时候调用。

let r = refWrapper.current.lastElementChild.getBoundingClientRect()
refWrapper.current.style.width = (r.x+r.width )+'px'

`

其中 refWrapper 是您的 flex 元素

答案 6 :(得分:-1)

由于尚未提出解决方案或适当的解决方法的建议,因此我设法使用了一些不同的方法来获得请求的行为。我没有将布局分为3个不同的div,而是将所有项目添加到1 div中,并在其中添加了一些div来创建分隔。

假设我们有3个部分,则所提出的解决方案是硬编码的,但可以扩展为通用的部分。主要思想是解释我们如何实现这种布局。

  1. 将所有项目添加到1个使用flex包裹项目的容器div中
  2. 每个“内部容器”的第一项(我将其称为一个部分)将具有一个类,该类有助于我们进行一些操作以创建每个部分的分隔和样式。
  3. 在每个第一项上使用:before,我们可以找到每个部分的标题。
  4. 使用space会在各部分之间形成空白
  5. 由于space不会覆盖该部分的整个高度,因此我还要在这些部分中添加:after,以便将其定位为绝对位置和白色背景。
  6. 要设置每个部分的背景颜色,请在每个部分的第一项内添加另一个div。我也将处于绝对位置,并将拥有z-index: -1
  7. 要获取每个背景的正确宽度,我使用了JS,设置了正确的宽度,并添加了一个监听器以调整大小。

function calcWidth() {
  var size = $(document).width();
  var end = $(".end").offset().left;

  var todoWidth = $(".doing-first").offset().left;
  $(".bg-todo").css("width", todoWidth);

  var doingWidth = $(".done-first").offset().left - todoWidth;
  $(".bg-doing").css("width", doingWidth);

  var doneWidth = $(".end").offset().left - $(".done-first").offset().left;
  $(".bg-done").css("width", doneWidth + 20);

}

calcWidth();

$(window).resize(function() {
  calcWidth();
});
.container {
  display: flex;
  flex-wrap: wrap;
  flex-direction: column;
  height: 120px;
  align-content: flex-start;
  padding-top: 30px;
  overflow-x: auto;
  overflow-y: hidden;
}

.item {
  width: 200px;
  background-color: #e5e5e5;
  border-radius: 5px;
  height: 20px;
  margin: 5px;
  position: relative;
  box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.75);
  padding: 5px;
}

.space {
  height: 150px;
  width: 10px;
  background-color: #fff;
  margin: 10px;
}

.todo-first:before {
  position: absolute;
  top: -30px;
  height: 30px;
  content: "To Do (2)";
  font-weight: bold;
}

.doing-first:before {
  position: absolute;
  top: -30px;
  height: 30px;
  content: "Doing (5)";
  font-weight: bold;
}

.doing-first:after,
.done-first:after {
  position: absolute;
  top: -35px;
  left: -25px;
  width: 10px;
  height: 180px;
  z-index: 10;
  background-color: #fff;
  content: "";
}

.done-first:before {
  position: absolute;
  top: -30px;
  height: 30px;
  content: "Done (3)";
  font-weight: bold;
}

.bg-todo {
  position: absolute;
  background-color: #FFEFD3;
  width: 100vw;
  height: 150px;
  top: -30px;
  left: -10px;
  z-index: -1;
}

.bg-doing {
  position: absolute;
  background-color: #EFDCFF;
  width: 100vw;
  height: 150px;
  top: -30px;
  left: -15px;
  z-index: -1;
}

.bg-done {
  position: absolute;
  background-color: #DCFFEE;
  width: 10vw;
  height: 150px;
  top: -30px;
  left: -15px;
  z-index: -1;
}

.end {
  height: 150px;
  width: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">

  <div class="item todo-first">
    <div class="bg-todo"></div>
    Drink coffee
  </div>
  <div class="item">Go to work</div>

  <div class="space"></div>

  <div class="item doing-first">
    <div class="bg-doing"></div>
    1
  </div>
  <div class="item">2</div>
  <div class="item">3</div>
  <div class="item">4</div>
  <div class="item">5</div>

  <div class="space"></div>

  <div class="item done-first">
    <div class="bg-done"></div>
    1
  </div>
  <div class="item">2</div>
  <div class="item">3</div>

  <div class="end"></div>

</div>

答案 7 :(得分:-2)

可能的JS解决方案..

.stop()