当 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."
更多分析
-
Mark Amery's answer
描述相同问题的其他帖子
- 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 个使用 flex 包装项目的容器div
- 每个 "inner container" 的第一项(我将其称为一个部分)将有一个 class,这有助于我们进行一些操作来创建每个部分的分离和样式。
- 在每个第一项上使用
:before
,我们可以找到每个部分的标题。
- 使用
space
在部分之间创建间隙
- 由于
space
不会覆盖该部分的整个高度,我还将 :after
添加到这些部分,以便使用绝对位置和白色背景定位它。
- 为了设置每个部分的背景颜色样式,我在每个部分的第一项内添加了另一个 div。我也会使用 absolute 的位置,并且会有
z-index: -1
.
- 为了获得每个背景的正确宽度,我使用了 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: wrap
和 wrap-reverse
。
我正在处理一个嵌套的 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."
更多分析
Mark Amery's answer
描述相同问题的其他帖子
- 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 个使用 flex 包装项目的容器div
- 每个 "inner container" 的第一项(我将其称为一个部分)将有一个 class,这有助于我们进行一些操作来创建每个部分的分离和样式。
- 在每个第一项上使用
:before
,我们可以找到每个部分的标题。 - 使用
space
在部分之间创建间隙 - 由于
space
不会覆盖该部分的整个高度,我还将:after
添加到这些部分,以便使用绝对位置和白色背景定位它。 - 为了设置每个部分的背景颜色样式,我在每个部分的第一项内添加了另一个 div。我也会使用 absolute 的位置,并且会有
z-index: -1
. - 为了获得每个背景的正确宽度,我使用了 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 中亲自测试过。可能有一种方法可以修改它以使其在其他浏览器中工作,或者已经对所述浏览器进行了更新以使其工作。
对这条评论大喊大叫:
我以这种方式解决了我的问题,希望它能帮助其他偶然发现问题的人:
_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: wrap
和 wrap-reverse
。