【问题标题】:When flexbox items wrap in column mode, container does not grow its width当 flexbox 项目以列模式换行时,容器不会增加其宽度
【发布时间】:2021-04-03 20:53:30
【问题描述】:

我正在研究一个嵌套的 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 显示了我目前所拥有的。

预期结果 (我想要的)

实际结果 (我得到的)

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

更新

经过一些调查,这开始看起来像是一个更大的问题。 所有主流浏览器的行为方式都相同,这与我的 flexbox 设计嵌套无关。更简单的 flexbox 列布局在项目换行时拒绝增加列表的宽度。

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

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

有人能想出解决这个问题的方法吗?

更新 2

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

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

【问题讨论】:

  • 只是一个旁注,但 Chrome 中存在一个错误,导致其流程设置为 column wrap 的容器在包装时不会扩展其宽度。请参阅code.google.com/p/chromium/issues/detail?id=247963 了解更多信息。
  • 我也无法让它在 IE11 中工作。在那里,最里面的项目会换行,但内部列表及其容器(外部项目)不会增加它们的宽度。目前我正在回退到让内部列表滚动,但这不是一个好的长期解决方案,我真的很想避免为此添加 JavaScript。
  • codepen.io/iugo/pen/ExWwjRY flex 和 wrap 时,如果是 row,height 可以自动调整。如果列,宽度不能自动适应,是单个最大宽度。

标签: html css flexbox


【解决方案1】:

问题

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

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

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

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

怎么可能所有主流浏览器都拥有 flex 容器 在行方向上展开换行,但不在列方向上展开?

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

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


问题说明

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


解决方法选项

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


更多分析


描述相同问题的其他帖子

【讨论】:

  • 根据错误报告 cmets,直到他们发布新的布局引擎LayoutNG
  • 您提供和分类的链接数量非常多。我希望大多数人都这样写答案。很好的答案。
  • 这是一个有用的 repo,列出了几个 Flexbox 错误和解决方法:This bug is listed at #14
  • 另一种解决方法:codepen.io/_mc2/pen/PoPmJRP 在某些方面我发现写模式方法更好
  • 知道在 2020 年,前端仍然像 2000 年一样是一个 PITA,有点奇怪的是令人欣慰——只是有一组完全不同的错误。
【解决方案2】:

聚会迟到了,但几年后仍然遇到这个问题。最终找到了使用网格的解决方案。在你可以使用的容器上

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

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

【讨论】:

  • 这是一个优雅的解决方法,如果我见过的话!我刚刚进入网格,我喜欢你的解决方案,谢谢你。
  • 看准了!解决了我的问题。
  • 在 Chrome 中仍然遇到这个问题。我希望避免使用网格解决方法。很高兴知道其他人正在使用这种方法!
  • 救命稻草。如果您需要某些项目占用更多行,也可以使用grid-row-end: span X;
  • 你成就了我的一天!谢谢!
【解决方案3】:

我刚刚在这里找到了一个非常棒的 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。

【讨论】:

  • 很棒的提示。我已经在 Firefox 中尝试过,它部分地在那里工作。在小提琴中,容器的红色背景只是其填充的宽度。如果我删除它的 display: inline-block,它就会变成页面的宽度。足够好,就我的目的而言。
  • 这是一个很好的解决方案!谢谢你帮了很多忙!!!!
  • 有效!这个答案很难找到,值得更多的支持!
【解决方案4】:

纯 CSS 解决方法

在提出这个问题将近 6 年后,这个 flexbox 错误仍然存在,所以这里有一个纯 CSS 的 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

【讨论】:

  • 太棒了,谢谢。这应该是公认的答案。
  • ** 请注意,您必须将书写模式和方向更改回按钮中的内容才能在 Firefox 中使用
【解决方案5】:

很遗憾,这么多主流浏览器多年后都出现了这个错误。考虑一个 Javascript 解决方法。每当浏览器窗口调整大小或将内容添加到元素时,执行此代码以将其调整为适当的宽度。你可以在你的框架中定义一个指令来为你做这件事。

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

【讨论】:

    【解决方案6】:

    由于尚未提出解决方案或适当的解决方法,我设法通过稍微不同的方法获得了所请求的行为。我没有将布局分成 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】:

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

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

      }

      【讨论】:

        【解决方案8】:

        解决方法:

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

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

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

        `

        refWrapper 是你的弹性元素

        【讨论】:

          【解决方案9】:

          可能的 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");
               }
          }
          

          【讨论】:

          • 不幸的是,这无济于事,因为各个元素的高度可能会有所不同。所以元素的数量并不有趣......但是使用columns 样式属性可能是一个可行的解决方案的起点。也许调整列数 ul的宽度。
          • 我最终也为我一直在开发的 React 应用程序使用了一个 JS 解决方案来解决这个问题。它旨在与任何和不同大小的元素一起使用。此处示例:djskinner.github.io/react-column-wrap。源码在这里:github.com/djskinner/react-column-wrap
          猜你喜欢
          • 2015-11-10
          • 1970-01-01
          相关资源
          最近更新 更多