Flex-shrink not working as expected - html

I'm starting to work with flexbox, and, in order to understand flex-grow and flex-shrink, I used a simple program that displays two blocks and makes them take up the whole width using flex-grow: 2 in one of them and flex-grow: 1 in the other.
If I check the following line in the console: $(".one").width() === $(window).width() /3 it returns true. So far, so good.
The problem appears when I reduce the window size, because as soon as I do this the same line in the console ($(".one").width() === $(window).width() /3) starts returning false.
I know the default value for flex-shrink is 1. Wouldn't that mean that the proportions between both blocks would be maintained (since they are both being shrunk by the same amount)? Can anyone explain why this result happens?
Here's my code:
* {
font-family: verdana;
margin: 0;
}
body {
background: #eee;
}
.wrapper {
width: 100%;
max-width: 2000px;
margin: 0 auto;
}
.flex-container {
display: flex;
background-color: white;
}
.box {
height: 100px;
}
.one {
background-color: red;
flex-grow: 1;
}
.two {
background-color: blue;
flex-grow: 2;
}
<div class="wrapper">
<div class="flex-container">
<div class="box one"></div>
<div class="box two"></div>
</div>
</div>

While not relevant to your question it might be worth noting:
flex-wrap takes precedence over flex-shrink, unless the item is wider than the container.
So if you have flex-wrap enabled and you're expecting two items to shrink to fit side by side on a single row they won't, they'll wrap first even if there's plenty of shrink-potential.
If the item is wider than the parent container it can't wrap so it will shrink if it can.
You'd have to solve this with min/max widths, make the initial size smaller (this is probably the best way) or creating a new parent container without flex-wrap.
See also Is there any use for flex-shrink when flex-wrap is wrap?

flex-shrink is designed to distribute negative free space in the container.
In other words, it only works when flex items are big enough to overflow the container.
You're not having that problem here. There is no negative space. Therefore, I don't believe flex-shrink is having any effect on your layout.
flex-grow is consuming the positive free space and seems to be working fine.
You would need to set a flex-basis or add content to the items to put flex-shrink in play.
https://www.w3.org/TR/css-flexbox-1/#flex-property

This is related to float calculations. Your flex code is working perfectly fine, the problem arises from the arithmetic operation, where the width of the container might not perfectly divide to 3, so the result will be a floating number which might or not be rounded to the closest number, so that's why width of your first flexbox item might not be equal to width / 3 because of that rounding.
Tested with Chrome Inspector.

Take a look at https://css-tricks.com/snippets/css/a-guide-to-flexbox/#article-header-id-13
They suggest using the shorthand flex: [number]; because it intelligently sets the default flex-shrink to 0. Just because the default for flex-shrink is 1 doesn't mean that 1 is what you want. I haven't been using flexbox that long, but I've yet to come across a scenario in which I've had to specify a flex-shrink. 0 has been working for me thus far. Maybe somebody else can provide a scenario for using it.
TLDR
Use flex attribute instead of flex-grow

Related

Make a div scroll instead of expanding to fit its contents

I have a layout where I have a scrollable list of items in the center, with some stuff above and below it in a column. The list should take up as much empty space as is available to it in the column (I don't want to specify a specific height on it), and should scroll when the empty space in the column is not enough to fit all the items.
Here is a JSFiddle of the situation.
Notice that with just a few items in the scroller, it expands to fill the empty space in the column (as is intended). But then if you add several more items to the scroller, it expands to fit the entirety of its contents instead of staying at its original size, and then scrolling its overflowing contents; even though the it has overflow-y: scroll set!
However, if you then set the scroller to have a height of 0, the problem is fixed and the items scroll as is intended, with the scroller at its original height before the extra items were added.
But WHY!? Can someone please explain to me what's going on here? Also, is there any consequences of this "solution" that I'm not seeing?
<div class="column">
<div class="title">Header</div>
<div class="scroller">
<div class="item">Child</div>
<div class="item">Child</div>
</div>
<div class="title">Footer</div>
</div>
,
.column {
display: flex;
flex-direction: column;
height: 200px;
}
.title {
height: 50px;
flex-shrink: 0;
}
.scroller {
flex-grow: 20;
flex-shrink: 0;
overflow-y: scroll;
}
.item {
height: 20px;
margin-top: 2px;
}
Some quick background for anyone who runs across this later:
Elements that have flex-grow expand to take up x units of the available space minus the other flex content. In your case, .scroller is the only one with flex-grow but the other flex elements have defined heights so their content takes up space.
Elements that have flex-shrink contract as the space decreases. A zero value means they don't contract, a value >=1 allows scaling down.
However flex-shrink ONLY works if the element DOES NOT also have a flex-grow applied to it. Elements with both shrink & grow will only shrink to the size of their content
In your example, overflow doesn't kick in when the element is as big as the content (see above) which it is because you have both grow/shrink applied. Adding an explicit height (height: 0) overrides the computed "content" height allowing the flex-shrink to compress the element smaller than its content. This, in turn, makes the scrollbar work.
I don't know if this will cause any oddities at some point but it's an interesting solution to the problem and does seem to work pretty well.
You need to add a height to your scroller container css:
height: 30px;
Or max-height to allow growth to a limit:
max-height: 30px;
Either way, for the scroll to kick in, the container needs to feel constrained, so maybe:
height: 100%;
Then limit the size of its container.
I was able to get this working by setting flex-shrink on the scroller element to 1. It will cap out at its available space and use the scrollbar.

How to create a flex item that shrinks before it wraps?

As can be seen in this JS-Fiddle, I basically try to use this CSS to create two divs that should fullfill these requirements:
If twice the space of the wider item is available, both should use 50% width (that works)
If not enough space for both items is available, they should wrap (that works)
If enough space is available for both items, but less than twice the width of the wider one, the narrower item should shrink (that does NOT work, it wraps)
I don't understand this behavior, because I have set flex-shrink for the flex items, so they should be able to shrink - but they don't: If the narrower item would be less than 50% wide, it wraps.
.m {
display: flex;
flex-wrap: wrap;
}
.l_1 {
background-color: red;
flex: 1 1 50%;
}
.r_1 {
background-color: yellow;
flex: 1 1 50%;
}
<div class=m>
<div class=l_1>
left_______________________________________________X
</div>
<div class=r_1>
right
</div>
</div>
(Tested on Firefox and Chrome)
The problem is not flex-shrink. The problem is flex-basis. You have it set to 50%.
This means that flex-grow will add free space to the flex-basis, and each item will wrap quickly, before it has an opportunity to shrink.
Switch from flex-basis: 50% to flex-basis: 0.
More specifically, instead of flex: 1 1 50% use flex: 1, which breaks down to this:
flex-grow: 1
flex-shrink: 1
flex-basis: 0
Now flex-grow distributes free space equally – not proportionally – to both items, and they can shrink before they wrap.
(Here's a more in-depth explanation: Make flex-grow expand items based on their original size)
.m {
display: flex;
flex-wrap: wrap;
}
.l_1 {
background-color: red;
flex: 1;
}
.r_1 {
background-color: yellow;
flex: 1;
}
<div class=m>
<div class=l_1>left_______________________________________________X</div>
<div class=r_1>right</div>
</div>
revised fiddle
When wrapping is enabled, it takes the place of shrinking, so where there is a condition that would trigger shrinking, it wraps instead - until there is only one item in the row, and if that's still bigger than the container, then it will shrink.
So, you need to set flex-basis for all boxes to the minimum size that block should be before wrapping. Note: a box will never shrink further than its minimum content width, meaning you can set flex-basis to 0 and it will go by the minimum content width of each box.
Then, if you want the boxes to expand to fill the available space, then use the flex-grow property (first value in flex) to control the relative amount by which each one should grow.
Flex-shrink will not apply with flex-wrap: wrap also applied. It will line wrap instead.
The only exception to this is when you only have one flex item in the row. Then it should allow for flex-shrink to apply.
Perhaps the appropriate fix is to only apply the flex-wrap within a media query, so it only happens in smaller viewports?

`flex-basis: auto` sizes parent as if child were `flex-basis: auto`, when child is `flex-basis:10px`

For example, you shouldn't be able to see the red of the parent here, but you do, because parent: 0 0 auto is sizing the parent to the auto width of its child content. You can see clearly though, the real width of its content is 10px, so shouldn't its auto sizing make the parent 10px as well?
body{
display:flex;
}
#parent {
flex: 0 0 auto;
background-color: red;
}
#child {
flex: 0 0 10px;
background-color: grey;
}
div{ display:flex; min-width:0; min-height:0; overflow:hidden; } /*There's some weirdness described here, http://stackoverflow.com/questions/36247140/why-doesnt-flex-item-shrink-past-content-size where flex elements default to min-width:auto, which could have caused problems here, but this doesn't change anything, so apparently this is not the issue*/
<div id="parent">
<div id="child">childcontents</div>
</div>
This occurs in firefox and chrome, so presumably this is going to turn out to be correct somehow. I'd like to know how, so that I can stop it.
According to my understanding of the spec, this is a bug.
What Michael_B said is correct, first #parent is sized, and once its width is know, #child can flex. Since flexing usually involves growing or shrinking, the size of the flex container must be known before flexing the flex item; and the final size of the flex item may not be the flex basis, so the flex container shouldn't be sized using that value.
The solution is easy: use width instead of flex-basis. width does not flex, so it doesn't depend on the width of the container. Thus the container can use the width of their contents when sized.
body {
display: flex;
}
#parent {
flex: none;
background-color: red;
}
#child {
width: 10px;
flex: none;
background-color: grey;
}
div {
display: flex;
min-width: 0;
min-height: 0;
overflow: hidden;
}
<div id="parent">
<div id="child">childcontents</div>
</div>
That said, in your case using flex-basis should work. That's because your flex item has both a zero flex grow factor and a zero flex shrink factor. It cannot grow nor shrink, it becomes directly frozen. Therefore it's possible to use consider the flex-basis when sizing the parent, and the spec says so:
9.9.3. Flex Item Intrinsic Size Contributions
The main-size min-content/max-content contribution of a
flex item is its outer min-content/max-content size,
clamped by its flex base size as a maximum (if it is not
growable) and/or as a minimum (if it is not shrinkable), and then
further clamped by its min/max main size properties.
The contribution of the ungrowable unshrinkable flex item is clamped by its flex base size both as a maximum and as a minimum. That is, the contribution is exactly the flex base size, which is defined as the flex basis when the flex basis is definite.
It looks like the flex layout algorithm calculates the width of the flex container before arriving at the width of the flex items.
Hence, it determines the auto size of #parent based on the full width of #child.
Then it sizes #child to flex-basis: 10px.
At his point, however, the auto width of the parent has already been determined and is inflexible.
Testing in Chrome, re-arranging the rules makes no difference, so it doesn't appear to be a cascading issue.
This is my view of the behavior without an official reference to back it up. You may find the precise answer here: W3C Spec: Flex Layout Algorithm

How to prevent a flex item from shrinking smaller than its content?

I've set up a jsfiddle to demonstrate the problem here:
http://jsfiddle.net/x0eo3aeo/2/
HTML:
<div class="flexContainer">
<div class="flexCol1">aaa</div>
<div class="flexCol2"><div style="width:100px; background-color:yellow;">bbb</div></div>
<div class="flexCol3"><div style="width:250px; background-color:pink;">Hello world, some long text here to make this element stay at 250px while splitting onto multiple lines.</div></div>
</div>
CSS:
.flexContainer {
display: flex;
width: 100%;
flex-direction: row;
}
.flexCol1, .flexCol3 {
flex: 1;
background-color: green;
}
Firefox actually behaves exactly how I want. Columns 1 and 3 flex equally until the width of column 3 hits the fixed size of its child div, and then only column 1 is flexed. However, in Chrome, both columns continue to flex equally and the child content of column 3 overflows.
Is there a way to achieve the Firefox-style behaviour in a cross-browser way?
You should be able to achieve the Firefox behavior in Chrome by adding min-width: -webkit-min-content; to .flexCol3. This prevents it from shrinking below its min-content width. (This is what's supposed to happen by default, due to min-width:auto introduced in the flexbox spec, but that hasn't been implemented in Chrome yet.)
As noted in comments below, IE doesn't seem to have a min-content width keyword implemented, so you might have to do something hackier there (like min-width: 250px). Fortunately, IE's next release (12?) does have min-width:auto implemented, so this should Just Work like Firefox there, I'm told.

How can I make a display:flex container expand horizontally with its wrapped contents?

When using css flexbox the three main browsers appear to behave entirely differently in certain areas.
In this case I am trying to create a grid of images:
<div class="container">
<div class="photo"></div>
<div class="photo"></div>
<div class="photo"></div>
<div class="photo"></div>
<div class="photo"></div>
<div class="photo"></div>
</div>
.container {
display:inline-flex;
flex-flow : column wrap;
align-content : flex-start;
height : 100%;
}
In this example I need a container, itself containing several div elements set up to flow from top to bottom and wrapping when they reach the bottom. Ultimately providing me with columns of photos.
However I need the container to expand horizontally to accommodate the wrapped elements:
Here is a quick jsFiddle to demonstrate.
The behaviour is as follows:
IE 11 - Correct, the container stretches horizontally to wrap each column of wrapped elements
Firefox - The container only wraps the first column of elements, with the rest overflow out.
Chrome - The container always stretches to fill the width of its parent, whatever that may be.
In this instance I would like to achieve the behaviour of IE11 in the other two browsers. Therefore my question is, how can I make a flexbox container expand horizontally to match its column wrap contents.
Thanks in advance.
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. This will swap the block direction with the inline direction, and thus the flex items will flow vertically. Then you only need to restore the horizontal writing mode inside the flex items.
.container {
display: inline-flex;
writing-mode: vertical-lr;
flex-wrap: wrap;
align-content: flex-start;
height: 350px;
background: blue;
}
.photo {
writing-mode: horizontal-tb;
width: 150px;
height: 100px;
background: red;
margin: 2px;
}
<div class="container">
<div class="photo">1</div>
<div class="photo">2</div>
<div class="photo">3</div>
<div class="photo">4</div>
<div class="photo">5</div>
<div class="photo">6</div>
<div class="photo">7</div>
<div class="photo">8</div>
<div class="photo">9</div>
</div>
This approach may have its own bugs in edge cases, especially if you mix advanced layout techniques like floats and nested flexboxs. But for most cases it seems to work properly.
The spec says that what you're doing should work, but it's implemented incorrectly in every major browser besides Internet Explorer / Edge, making multi-line inline-flex column layouts useless at present for most developers. Here's a Chromium bug report providing an example that is effectively identical to yours, and noting that it renders incorrectly in Chrome, Safari, and Firefox.
The argument from spec is more complicated than I'm able to understand, but the key point is that Flexible Box Layout Module Level 1 spec defines the intrinsic cross-size of a flex container (that is, the intrinsic height of a flex-direction: row flex container or the intrinsic width of a flex-direction: column flex container) in the section Flex Container Intrinsic Cross Size. There, it is stated:
For a multi-line flex container, the min-content/max-content cross size is the sum of the flex line cross sizes
That is, the intrinsic width of a flex-direction: column flex container should be the sum of the widths of its columns, as you'd expect. (There is more complexity than this, and I don't understand it all, but I believe the above to be broadly true.) However, Chrome, Firefox, and Safari all calculate this width incorrectly; setting width: min-content or width: max-content on a column wrap flex box in Chrome, you can clearly see that the width is set to the width of the widest single element.
A silly Chrome-specific workaround exists, but is probably best avoided. Until the bug is fixed, this part of the Flexbox model simply doesn't work as designed and there's no clean solution available.
It seems this issue cannot be solved only with CSS, so I propose you a JQuery solution
container width = position of the last child - position of the container + width of the last child (including margin)
Code :
$(document).ready(function() {
$('.container').each(function( index ) {
var lastChild = $(this).children().last();
var newWidth = lastChild.position().left - $(this).position().left + lastChild.outerWidth(true);
$(this).width(newWidth);
})
});
Demo :
http://jsfiddle.net/qzea320L/
You have a column layout distribution with a fixed height container.
When you set the flex-direction to column you define the Vertical axis as the main axis.
In flexbox that means it will fill up the available height and then create a new column.
In this JSBIN I use javascript to change the container's height and, because of that, you will see the child items move.
PS: you shouldn't rely on IE behavior since their flex support is recent.
Another possible approach:
.container {
column-count: 2; /*or whatever */
}
.container > div {
display: inline-block;
}
https://developer.mozilla.org/en-US/docs/Web/CSS/column-count
You may also need to adjust margin-top of .container > div:first-child if they don't align to the top.