我正在研究一个嵌套的 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 显示了我到目前为止所拥有的。
预期结果(我想要的):
https://i.stack.imgur.com/urybZ.png
实际结果(我得到的):
https://i.stack.imgur.com/YAVx2.png
较旧的结果(我在 2015 年得到的结果):
https://i.stack.imgur.com/t5SQd.png
更新
经过一番调查,这开始看起来像是一个更大的问题。所有主流浏览器的行为方式都相同,这与我的 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
,以便内部容器在需要时垂直滚动。它并不漂亮,但它是一种至少不会过多破坏可用性的前进方式。
column wrap
的容器在包装时不会扩展其宽度。有关详细信息,请参阅 code.google.com/p/chromium/issues/detail?id=247963。
问题
这看起来像是 flex 布局的一个根本缺陷。
列方向的 flex 容器不会扩展以容纳额外的列。 (这在 flex-direction: row
中不是问题。)
这个问题已经被问过很多次了(见下面的列表),在 CSS 中没有明确的答案。
很难将其视为错误,因为该问题发生在所有主要浏览器中。但它确实提出了一个问题:
怎么可能所有主流浏览器都让 flex 容器在行方向上展开而不是在列方向上展开?
你会认为其中至少有一个会做对。我只能推测原因。也许这是一个技术上难以实现的实现,并且在这次迭代中被搁置了。
更新:该问题似乎在 Edge v16 中得到解决。
问题说明
OP 创建了一个有用的演示来说明该问题。我在这里复制它:http://jsfiddle.net/nwccdwLw/1/
解决方法选项
来自 Stack Overflow 社区的 Hacky 解决方案:
“看来这个问题只用CSS是不能解决的,所以我建议你一个JQuery的解决方案。”
“奇怪的是,大多数浏览器都没有正确实现列弹性容器,但对写入模式的支持相当不错。因此,您可以使用垂直写入模式的行弹性容器。”
更多分析
铬错误报告
马克艾默里的回答
描述相同问题的其他帖子
Flex box 容器宽度没有增长
如何使 display:flex 容器水平扩展其包装的内容?
Flex-flow:列换行。如何设置容器的宽度等于内容?
Flexbox flex-flow 列在 chrome 中包装错误?
如何使用“flex-flow:列换行”?
内容换行时,Flex 容器不会展开
flex-flow:列换行,在 flex 框中导致父容器溢出
Html flexbox 容器不会在包裹的子项上展开
Flexbox 容器和溢出的 flex 子对象?
如何制作一个可以伸展以适应包裹物品的 flexbox 容器?
Flex容器计算一列,当有多列时
使用 flex 使容器全宽
Flexbox 容器可以调整大小吗?
Flex-Wrap 内部 Flex-Grow
Flexbox 增长到包含
将 flexbox 元素扩展到其内容?
flexbox 列拉伸以适应内容
https://stackoverflow.com/q/48406237/3597276
flex-flow:列换行不会拉伸父元素的宽度
为什么我的
https://stackoverflow.com/q/55709208/3597276
Flexbox wrap 不会增加父级的宽度?
绝对 Flex 容器未更改为具有定义的最大高度的正确宽度
聚会迟到了,但几年后仍然遇到这个问题。最终找到了使用网格的解决方案。在您可以使用的容器上
display: grid;
grid-auto-flow: column;
grid-template-rows: repeat(6, auto);
我在 CodePen 上有一个在 flexbox 问题和网格修复之间切换的示例:https://codepen.io/MandeeD/pen/JVLdLd
grid-row-end: span X;
。
我刚刚在这里找到了一个非常棒的 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。
仅限 CSS 的解决方法
在提出这个问题将近 6 年后,这个 flexbox 错误 仍然 存在,所以这里有一个纯 CSS flex-direction: column
解决方法供其他任何人使用:
身体{背景颜色:灰色; } 按钮 { 背景颜色:白色;边框:无;边框半径:4px;宽度:80px;高度:40px;边距:4px; } /* flex-direction 的解决方法:带有 WRAP 的列在下面 */ .wrapped-columns { flex-direction: row;弹性包装:换行;写作模式:垂直lr;文本方向:直立; } /* 确保内容在 Firefox 中正确呈现 */ .wrapped-columns * { writing-mode: Horizontal-tb; }
此解决方法提供与 flex-direction: column
相同的结果,并且适用于 flex-wrap: wrap
和 wrap-reverse
。
很遗憾,这么多主流浏览器多年后都出现了这个bug。考虑一个 Javascript 解决方法。每当浏览器窗口调整大小或将内容添加到元素时,执行此代码以使其大小调整为适当的宽度。你可以在你的框架中定义一个指令来为你做这件事。
element.style.flexBasis = "auto";
element.style.flexBasis = `${element.scrollWidth}px`;
由于尚未提出解决方案或适当的解决方法,因此我设法通过稍微不同的方法获得了所请求的行为。我没有将布局分成 3 个不同的 div,而是将所有项目添加到 1 个 div 中,并在它们之间创建更多 div 的分隔。
建议的解决方案是硬编码的,假设我们有 3 个部分,但可以扩展到一个通用的部分。主要思想是解释我们如何实现这种布局。
将所有项目添加到使用 flex 包装项目的 1 个容器 div 每个“内部容器”的第一个项目(我将称之为部分)将有一个类,这有助于我们进行一些操作来创建分离和每个部分的样式。在每个第一个项目上使用 :before,我们可以找到每个部分的标题。使用空间会在部分之间创建间隙由于空间不会覆盖部分的整个高度,因此我还要添加 :after 到部分,因此将其定位为绝对位置和白色背景。为了设置每个部分的背景颜色,我在每个部分的第一项中添加了另一个 div。我也将处于绝对位置,并且将具有 z-index:-1。为了获得每个背景的正确宽度,我使用 JS,设置正确的宽度,并添加一个监听器来调整大小。
函数 calcWidth() { var size = $(document).width(); var end = $(".end").offset().left; var todoWidth = $(".doing-first").offset().left; $(".bg-todo").css("宽度", 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("宽度", doneWidth + 20); } 计算宽度(); $(window).resize(function() { calcWidth(); }); .container { 显示:弹性;弹性包装:换行;弹性方向:列;高度:120px;对齐内容:弹性开始;填充顶部:30px;溢出-x:自动;溢出-y:隐藏; } .item { 宽度:200px;背景颜色:#e5e5e5;边框半径:5px;高度:20px;边距:5px;位置:相对;盒子阴影:1px 1px 5px 0px rgba(0, 0, 0, 0.75);填充:5px; } .space { 高度:150px;宽度:10px;背景颜色:#fff;边距:10px; } .todo-first:before { 位置:绝对;顶部:-30 像素;高度:30px;内容:“待办事项(2)”;字体粗细:粗体; } .doing-first:before { 位置:绝对;顶部:-30 像素;高度:30px;内容:“做(5)”;字体粗细:粗体; } .doing-first:after, .done-first:after { position: absolute;顶部:-35px;左:-25px;宽度:10px;高度:180px; z 指数:10;背景颜色:#fff;内容: ””; } .done-first:before { 位置:绝对;顶部:-30 像素;高度:30px;内容:“完成(3)”;字体粗细:粗体; } .bg-todo { 位置:绝对;背景颜色:#FFEFD3;宽度:100vw;高度:150px;顶部:-30 像素;左:-10px; z 指数:-1; } .bg-doing { 位置:绝对;背景颜色:#EFDCFF;宽度:100vw;高度:150px;顶部:-30 像素;左:-15px; z 指数:-1; } .bg-done { 位置:绝对;背景颜色:#DCFFEE;宽度:10vw;高度:150px;顶部:-30 像素;左:-15px; z 指数:-1; } .end { 高度:150px;宽度:10px; }
我以这种方式解决了我的问题,我希望它可以帮助其他偶然发现的人:
_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 在屏幕上加载元素后手动设置包装器的宽度并不难。宽度始终是最后一个子元素的右手点。
在反应中,我根据添加到 flex 包装器的任何子级更新了布局更改,但这可以在您向包装器添加或删除子级的任何时候调用。
let r = refWrapper.current.lastElementChild.getBoundingClientRect()
refWrapper.current.style.width = (r.x+r.width )+'px'
`
其中 refWrapper 是你的弹性元素
可能的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 的宽度。