ChatGPT解决这个技术问题 Extra ChatGPT

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

I am working on a nested flexbox layout which should work as follows:

The outermost level (ul#main) is a horizontal list that must expand to the right when more items are added to it. If it grows too big, there should be a horizontal scroll bar.

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

Each item of this list (ul#main > li) has a header (ul#main > li > h2) and an inner list (ul#main > li > ul.tasks). This inner list is vertical and should wrap into columns when needed. When wrapping into more columns, its width should increase to make room for more items. This width increase should apply also to the containing item of the outer list.

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

My problem is that the inner lists don't wrap when the height of the window gets too small. I have tried lots of tampering with all the flex properties, trying to follow the guidelines at CSS-Tricks meticulously, but no luck.

This JSFiddle shows what I have so far.

Expected result (what I want):

https://i.stack.imgur.com/urybZ.png

Actual result (what I get):

https://i.stack.imgur.com/YAVx2.png

Older result (what I got in 2015):

https://i.stack.imgur.com/t5SQd.png

UPDATE

After some investigation, this is beginning to look like a bigger issue. All major browsers behave the same way, and it has nothing to do with my flexbox design being nested. Even simpler flexbox column layouts refuse to increase the list's width when the items wrap.

This other JSFiddle clearly demonstrates the problem. In current versions of Chrome, Firefox and IE11, all items wrap correctly; the list's height increases in row mode, but its width does not increase in column mode. Also, there is no immediate reflow of elements at all when changing the height of a column mode, but there is in row mode.

However, the official specs (look specifically at example 5) seem to indicate that what I want to do should be possible.

Can someone come up with a workaround to this problem?

UPDATE 2

After a lot of experimenting using JavaScript to update the height and width of various elements during resize events, I have come to the conclusion that it is too complex and too much trouble to try to solve it that way. Also, adding JavaScript definitely breaks the flexbox model, which should be kept as clean as possible.

For now, I'm falling back to overflow-y: auto instead of flex-wrap: wrap so that the inner container scrolls vertically when needed. It is not pretty, but it is one way forward that at least does not break useability too much.

Just a sidenote, but there's a bug in Chrome that causes a container with it's flow set to column wrap not to expand it's width when wrapping. See code.google.com/p/chromium/issues/detail?id=247963 for more information.
I can't get it to work in IE11 either. There, the innermost items wrap, but the inner list and its container (the outer item) do not increase their width. Currently I'm falling back to having the inner lists scroll, but it is not a good long-term solution, and I would really like to avoid adding JavaScript for this.
codepen.io/iugo/pen/ExWwjRY when flex and wrap, if row, height can autofit. if column, the width can not auto fit, is single max-width.

M
Michael Benjamin

The Problem

This looks like a fundamental deficiency in flex layout.

A flex container in column-direction will not expand to accommodate additional columns. (This is not a problem in flex-direction: row.)

This question has been asked many times (see list below), with no clean answers in CSS.

It's hard to pin this as a bug because the problem occurs across all major browsers. But it does raise the question:

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

You would think at least one of them would get it right. I can only speculate on the reason. Maybe it was a technically difficult implementation and was shelved for this iteration.

UPDATE: The issue appears to be resolved in Edge v16.

Illustration of the Problem

The OP created a useful demo illustrating the problem. I'm copying it here: http://jsfiddle.net/nwccdwLw/1/

Workaround Options

Hacky solutions from the Stack Overflow community:

"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."

More Analysis

Chromium Bug Report

Mark Amery's answer

Other Posts Describing the Same Problem

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?

How can I make a flexbox container that stretches to fit wrapped items?

Flex container calculating one column, when there are multiple columns

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

https://stackoverflow.com/q/48406237/3597276

flex-flow: column wrap doesn't stretch the parent element's width

Why doesn't my

    expand width to cover all the
  • ?

    https://stackoverflow.com/q/55709208/3597276

    Flexbox wrap not increasing the width of parent?

    Absolute Flex container not changing to the correct width with defined max-height


    According to the bug report comments, this issue won't be fixed until they release their new layout engine LayoutNG
    The number of links you provided and categorized them is really great. I wish most people write answers this way. Great answer.
    Here's a useful repo listing out several Flexbox bugs and workarounds for them: This bug is listed at #14
    one more workaround option: codepen.io/_mc2/pen/PoPmJRP For some aspect I found it better that write-mode approach
    It's somehow oddly comforting to know that in 2020, frontend is still a PITA like it was in 2000 – just with a totally different set of bugs.
M
MandeeD

Late to the party, but was still running into this issue YEARS later. Ended up finding a solution using grid. On the container you can use

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

I have an example on CodePen that toggles between the flexbox issue and the grid fix: https://codepen.io/MandeeD/pen/JVLdLd


That's an elegant workaround if I've ever seen one! I'm just getting into grid and I love your solution, thank you for this.
Spot on! Solved my issue.
Still running into this issue in Chrome. I was hoping to avoid using a grid workaround. Good to know others are using this approach!
life-saver. You can also use grid-row-end: span X; if you need some of the items to take up more rows.
I was so stuck thinking in terms of Flex that I completely forgot about Grids. This solves the problem! Thanks!!!!!
D
Dustin Kane

I just found a really awesome PURE CSS workaround here.

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

The tricky: set writing-mode: vertical-lr in the list div then writing-mode: horizontal-tb in the list item. I had to tweak the styles in the JSFiddle (remove a lot of the alignment styles, which aren't necessary for the solution).

Note: the comment says it only works in Chromium-based browsers, and not Firefox. I've only personally tested in Chrome. It's possible either there's a way to modify this to make it work in other browsers or there have been updates to said browsers that make this work.

Big shoutout to this comment: When flexbox items wrap in column mode, container does not grow its width. Digging through that issue thread led me to https://bugs.chromium.org/p/chromium/issues/detail?id=507397#c39 which led me to this JSFiddle.


Great tip. I've tried this in Firefox and it partly works there. In the fiddle, the container's red background is only the width of its padding. If I remove its display: inline-block, it becomes the width of the page. Good enough, for my purposes.
That is a GREAT solution! Thank you have helped a lot!!!!
Works! This answer is hard to find and deserves more upvotes!
N
Neil Rackett

CSS-only workaround

Nearly 6 years after this question was asked, this flexbox bug still exists, so here's a CSS-only flex-direction: column workaround for anyone else that ends up here:

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; }

This workaround gives the same outcome as flex-direction: column and works with both flex-wrap: wrap and wrap-reverse.


Amazing, thank you. This should be the accepted answer.
** Just to note you have to change the writing mode and the orientation back to what it was in the buttons for this to work in firefox
S
Steve Hanov

It is unfortunate that so many major browsers suffer from this bug after many years. Consider a Javascript workaround. Whenever the browser window resizes, or content is added to the element, execute this code to get it to resize to the proper width. You can define a directive in your framework to do it for you.

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

I
Itay Gal

Since no solution or proper workaround was suggested yet, I managed to obtain the requested behavior with a little different approach. Instead of separating the layout into 3 different divs, I'm adding all the items into 1 div and creating the separation with some more divs in between.

The proposed solution is hard coded, assuming we have 3 sections, but can be extended to a generic one. The main idea is to explain how we can achieve this layout.

Adding all the items into 1 container div that uses flex to wrap the items The first item of each "inner container" (I'll call it a section) will have a class, which helps us to do some manipulations that create the separation and styling of each section. Using :before on each first item, we can locate the title of each section. Using space creates the gap between the sections Since the space won't cover the full height of the section I'm also adding :after to the sections so positioning it with absolute position and white background. To style the background color of each section I'm adding another div inside the first item of each section. I will be position with absolute as well and will have z-index: -1. To get the correct width of each background, I'm using JS, setting the correct width, and also adding a listener to resize.

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; }

Drink coffee
Go to work
1
2
3
4
5
1
2
3


L
Lloyd Smith

I solved my issue in this manner I hope it helps someone else that stumbles on thid :

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

}


y
yeahdixon

Workaround :

using javascript its not hard to set the wrapper's width manually after elements have loaded on the screen. The width would always be the last child element's right hand point.

In react i have it updated on the layout changes based on any children being added to the flex wrapper , but this could be called at any point you add or remove children to the wrapper .

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

`

where refWrapper is your your flex element


u
user2323336

Possible JS solution..

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

That would not help, unfortunately, as the heights of individual elements can vary. So the number of elements is not interesting... But using the columns style property could be a starting point for a working solution. Maybe adjusting the number of columns and the width of the ul.
I also ended up going with a JS solution to this problem for a React application I have been working on. It is designed to work with elements of any and varying size. Example here: djskinner.github.io/react-column-wrap. Source code here: github.com/djskinner/react-column-wrap