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

When flexbox items wrap in column mode, container does not grow its width

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

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

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

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

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

我的问题是,当 window 的高度太小时,内部列表不会换行。我尝试了很多篡改所有 flex 属性的方法,试图一丝不苟地遵循 CSS-Tricks 上的指南,但没有成功。

这个 JSFiddle 展示了我目前所拥有的。

预期结果 (我想要的):

实际结果 (我得到的):

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

更新

经过一些调查,这似乎是一个更大的问题。 所有主流浏览器的行为方式都相同,这与我的嵌套 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,以便内部容器在需要时垂直滚动。它不是很漂亮,但它是一种前进的方式,至少不会过多地破坏可用性。

问题

这看起来像是 flex 布局的根本缺陷。

列方向的弹性容器不会扩展以容纳额外的列。 (这在 flex-direction: row 中不是问题。)

这个问题已被问过很多次(见下面的列表),在 CSS 中没有明确的答案。

很难将此确定为错误,因为所有主流浏览器都会出现该问题。但它确实提出了问题:

How is it possible that all major browsers got the flex container to expand on wrap in row-direction but not in column-direction?

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

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


问题说明

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


解决方法选项

来自 Stack Overflow 社区的 Hacky 解决方案:

  • "It seems this issue cannot be solved only with CSS, so I propose you a JQuery solution."

  • "It's curious that most browsers haven't implemented column flex containers correctly, but the support for writing modes is reasonably good. Therefore, you can use a row flex container with a vertical writing mode."


更多分析


描述相同问题的其他帖子

  • Flex box container width doesn't grow
  • How can I make a display:flex container expand horizontally with its wrapped contents?
  • Flex-flow: column wrap. How to set container's width equal to content?
  • Flexbox flex-flow column wrap bugs in chrome?
  • How do I use "flex-flow: column wrap"?
  • Flex container doesn't expand when contents wrap in a column
  • flex-flow: column wrap, in a flex box causing overflow of parent container
  • Html flexbox container does not expand over wrapped children
  • Flexbox container and overflowing flex children?
  • Make container full width with flex
  • Flexbox container resize possible?
  • Flex-Wrap Inside Flex-Grow
  • Flexbox grow to contain
  • Expand flexbox element to its contents?
  • flexbox column stretch to fit content
  • Why doesn't my <ul> expand width to cover all the <li>?
  • Flexbox wrap not increasing the width of parent?
  • Absolute Flex container not changing to the correct width with defined max-height

可能的 JS 解决方案..

var ul = $("ul.ul-to-fix");

if(ul.find("li").length>{max_possible_rows)){
    if(!ul.hasClass("width-calculated")){
        ul.width(ul.find("li").eq(0).width()*ul.css("columns"));
        ul.addClass("width-calculated");
     }
}

由于尚未提出任何解决方案或适当的变通办法,我设法通过一些不同的方法获得了请求的行为。我没有将布局分成 3 个不同的 div,而是将所有项目添加到 1 div 中,并在中间创建了更多 div 的分隔。

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

  1. 将所有项目添加到 1 个使用 flex 包装项目的容器div
  2. 每个 "inner container" 的第一项(我将其称为一个部分)将有一个 class,这有助于我们进行一些操作来创建每个部分的分离和样式。
  3. 在每个第一项上使用 :before,我们可以找到每个部分的标题。
  4. 使用 space 在部分之间创建间隙
  5. 由于 space 不会覆盖该部分的整个高度,我还将 :after 添加到这些部分,以便使用绝对位置和白色背景定位它。
  6. 为了设置每个部分的背景颜色样式,我在每个部分的第一项内添加了另一个 div。我也会使用 absolute 的位置,并且会有 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>

派对迟到了,但 运行 年后仍然关注这个问题。最终找到了使用网格的解决方案。在容器上你可以使用

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

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

不幸的是,许多主流浏览器在多年后仍遭受此错误。考虑 Javascript 解决方法。每当浏览器 window 调整大小,或向元素添加内容时,执行此代码以使其调整到适当的宽度。你可以在你的框架中定义一个指令来为你做这件事。

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

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

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

技巧:在列表 div 中设置 writing-mode: vertical-lr,然后在列表项中设置 writing-mode: horizontal-tb。我不得不调整 JSFiddle 中的样式(删除了很​​多对齐样式,这对于解决方案来说不是必需的)。

注意:评论说它只适用于基于 Chromium 的浏览器,而不适用于 Firefox。我只在 Chrome 中亲自测试过。可能有一种方法可以修改它以使其在其他浏览器中工作,或者已经对所述浏览器进行了更新以使其工作。

对这条评论大喊大叫:. Digging through that issue thread led me to https://bugs.chromium.org/p/chromium/issues/detail?id=507397#c39 这让我找到了这个 JSFiddle。

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

  _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`;

}

解决方法:

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

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

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

`

其中 refWrapper 是您的 flex 元素

CSS-仅解决方法

在提出这个问题将近 6 年后,这个 flexbox 错误 仍然 存在,所以这里有一个 CSS-only flex-direction: column 解决方法结束于此:

body {
  background-color: grey;
}

button {
  background-color: white;
  border: none;
  border-radius: 4px;
  width: 80px;
  height: 40px;
  margin: 4px;
}

/* WORKAROUND FOR flex-direction: column WITH WRAP IS BELOW */
.wrapped-columns {
  flex-direction: row;
  flex-wrap: wrap;
  writing-mode: vertical-lr;
  text-orientation: upright;
}

/* Ensures content is rendered correctly in Firefox */
.wrapped-columns * {
    writing-mode: horizontal-tb;
}
<div class="wrapped-columns">
  <button>Button 1</button>
  <button>Button 2</button>
  <button>Button 3</button>
  <button>Button 4</button>
  <button>Button 5</button>
  <button>Button 6</button>
  <button>Button 7</button>
  <button>Button 8</button>
  <button>Button 9</button>
  <button>Button 10</button>
  <button>Button 11</button>
  <button>Button 12</button>
  <button>Button 13</button>
  <button>Button 14</button>
</div>

此变通方法提供与 flex-direction: column 相同的结果,并且适用于 flex-wrap: wrapwrap-reverse