What's the difference between visibility: hidden and visibility: collapse in flexbox? - css

In the CSS flexible box layout module, it says:
the collapsed flex item is removed from rendering entirely, but leaves behind a "strut"
Does that behave just like visibility: hidden? If the answer is yes, then why was visibility: collapse introduced?

Note on browser support: As of July 2017, Chrome (59) does not support visibility: collapse. The code samples below with collapse work in Firefox and Edge, but fail in Chrome (they behave just like hidden). UPDATE: As of July 2020, this is note is still valid. Chrome and Safari treat visibility: collapse like hidden. caniuse.com
Flex items are laid out in a row or column, depending on flex-direction.
Each row / column is considered a flex line.
In the examples below, a flex container has four flex items in row-direction. The fourth item wraps, creating a second flex line:
.container {
display: flex;
flex-wrap: wrap;
width: 200px;
border: 1px dashed black;
}
.box {
height: 50px;
flex: 0 0 50px;
margin: 5px;
background-color: lightgreen;
display: flex;
justify-content: center;
align-items: center;
}
<div class="container">
<div class="box box1">1</div>
<div class="box box2">2</div>
<div class="box box3">3</div>
<div class="box box4">4</div>
</div>
display: none
With display: none, a flex item isn't rendered by the browser.
If all items on the flex line have display: none, the line also collapses, which affects the rest of the layout. Surrounding elements may shift when a flex line collapses.
With display: none applied to the third item, the fourth item takes its place on the upper line, and the lower line collapses:
.container {
display: flex;
flex-wrap: wrap;
width: 200px;
border: 1px dashed black;
}
.box {
height: 50px;
flex: 0 0 50px;
margin: 5px;
background-color: lightgreen;
display: flex;
justify-content: center;
align-items: center;
}
.box3 { display: none; }
<code>display: none</code>
<div class="container">
<div class="box box1">1</div>
<div class="box box2">2</div>
<div class="box box3">3</div>
<div class="box box4">4</div>
</div>
visibility: hidden
With visibility: hidden, a flex item is rendered by the browser but is fully transparent. It's hidden from view but takes up the space it would normally use in the layout. Hence, surrounding elements see this item as fully intact.
In this example, when the last two boxes have visibility: hidden, the rest of the layout (including the second flex line) remains unchanged.
.container {
display: flex;
flex-wrap: wrap;
width: 200px;
border: 1px dashed black;
}
.box {
height: 50px;
flex: 0 0 50px;
margin: 5px;
background-color: lightgreen;
display: flex;
justify-content: center;
align-items: center;
}
.box3 { visibility: hidden; }
.box4 { visibility: hidden; }
<code>visibility: hidden</code>
<div class="container">
<div class="box box1">1</div>
<div class="box box2">2</div>
<div class="box box3">3</div>
<div class="box box4">4</div>
</div>
visibility: collapse
With visibility: collapse, a flex item is not rendered (same as display: none), but the flex algorithm checks the cross size of the item and then uses that data to keep the flex line stable (i.e., what the cross size of the line would be if the flex item was visible).
The difference with display: none is that collapse allows one piece of the item – its cross size – to be preserved. This is referred to in the spec as the strut.
So if all flex items on the line have visibility: collapse, the cross size of the line (whether it be the width or height) does not collapse, and the rest of the layout isn't affected.
Note that although collapse guarantees the stability of the line's cross size, it provides no such assurance for the line's main size. This is a key difference between collapse and hidden.
Below are some examples. (As mentioned above, these won't work in Chrome. Test in FF or Edge.)
In this example, the first two items have visibility: collapse.
.container {
display: flex;
flex-wrap: wrap;
width: 200px;
border: 1px dashed black;
}
.box {
height: 50px;
flex: 0 0 50px;
margin: 5px;
background-color: lightgreen;
display: flex;
justify-content: center;
align-items: center;
}
.box1, .box2 {
visibility: collapse;
}
<code>visibility: collapse</code>
<div class="container">
<div class="box box1">1</div>
<div class="box box2">2</div>
<div class="box box3">3</div>
<div class="box box4">4</div>
</div>
The layout renders like display: none. The second line collapses because the main size of the items is gone, allowing the last item to move up naturally.
In the following example, all items get visibility: collapse. Hence, the second line collapses because the items' main size is gone, but the cross size of the first line remains.
.container {
display: flex;
flex-wrap: wrap;
width: 200px;
border: 1px dashed black;
}
.box {
height: 50px;
flex: 0 0 50px;
margin: 5px;
background-color: lightgreen;
display: flex;
justify-content: center;
align-items: center;
}
.box {
visibility: collapse;
}
<code>visibility: collapse</code>
<div class="container">
<div class="box box1">1</div>
<div class="box box2">2</div>
<div class="box box3">3</div>
<div class="box box4">4</div>
</div>
jsFiddle

It actually depends on the element. If used on a table sub-element, collapse will hide the element as well as the space it occupied.
collapse will behave like hidden if used on any element that is not a table sub-element

Related

Full-height flex item with height set to 0

I have the following code as working on development with flexbox.
#container {
display: flex;
justify-content: space-around;
width: 100%;
}
.content {
border: 1px solid black;
display: flex;
flex-direction: column;
height: 400px;
width: 400px;
}
#item1 {
background-color: red;
flex-grow: 1;
height: 0;
}
#item2 {
background-color: green;
flex-grow: 1;
height: 100px;
}
#item3 {
background-color: blue;
flex-grow: 1;
height: 900px;
}
<div id="container">
<div class="content">
<div id="item1"></div>
</div>
<div class="content">
<div id="item2"></div>
</div>
<div class="content">
<div id="item3"></div>
</div>
</div>
I know that setting flex-grow: 1 would take the remaining space of its parent. However, the property height seems to have no effect whatever its value is.
Reason being your flex-direction is set to column, which mean the flex-grow reacts from top to bottom, so the flex-grow responding to the height instead of width.
another question is, why flex-direction is column, but width is filled up, because it is a <div> displayed as block, the width is auto filled by display: block;
you are using flex-grow that’s why. have a look on this https://www.w3schools.com/cssref/css3_pr_flex-grow.asp
https://stackoverflow.com/a/64748435/1095913 (down here) is right, solution is: flex-grow: 0;
Here's another reference https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow

CSS: responsive grid/flex layout when there are intermediate divs between the parent and children? [duplicate]

I have a simple flex element with some children, something like this:
.container{
display: flex;
flex-wrap: wrap;
}
.container div.flex-box{
width: 200px;
margin: 20px;
padding: 20px;
font-size: 40px;
border: 1px solid;
}
<div class='container'>
<div class='flex-box'>one</div>
<div class='flex-box'>two</div>
<div class='flex-box'>three</div>
<div class='flex-box'>four</div>
<div class='flex-box'>five</div>
<div class='flex-box'>six</div>
</div>
I am using flex wrap so, when screen goes smaller they stack.
Now, I want to reload fourth and fifth element using an ajax call to reload both elements, for this I need to put both children inside a container, but when I do, it becomes a new flex child
.container{
display: flex;
flex-wrap: wrap;
}
.container div.flex-box{
width: 200px;
margin: 20px;
padding: 20px;
font-size: 40px;
border: 1px solid;
}
<div class='container'>
<div class='flex-box'>one</div>
<div class='flex-box'>two</div>
<div class='flex-box'>three</div>
<div class='subcontainer'>
<div class='flex-box'>four</div>
<div class='flex-box'>five</div>
</div>
<div class='flex-box'>six</div>
</div>
I am looking for a way to ingore this "subcontainer", and keep the children working as before.
Is this possible?
Use display:contents (https://css-tricks.com/get-ready-for-display-contents/) but pay attention to the support (https://caniuse.com/#feat=css-display-contents)
.container {
display: flex;
flex-wrap: wrap;
}
.container div.flex-box {
width: 200px;
margin: 20px;
padding: 20px;
font-size: 40px;
border: 1px solid;
}
.subcontainer {
display: contents
}
<div class='container'>
<div class='flex-box'>one</div>
<div class='flex-box'>two</div>
<div class='flex-box'>three</div>
<div class='subcontainer'>
<div class='flex-box'>four</div>
<div class='flex-box'>five</div>
</div>
<div class='flex-box'>six</div>
</div>
If the element is a child of a flex container, then it becomes a flex item. That's the general rule.
However, most browsers will ignore that rule when the child is absolutely positioned.
I'm not sure that's a useful solution in this case, but that's what you would have to do: absolutely position .subcontainer and make it flex container, so that the children become flex items.

CSS Flex dynamic grid with multiple sizes

I have a problem with flex.
I have a wrapper where a minimum of 1 and maximum of 9 squares can be shown. Squares can have multiple sizes, based on the number of squares in grid.
I've got all required cases working except for one, as seen in this picture:
My styles are:
.grid {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-content: space-between;
width: 300px;
height: 300px;
position: relative;
}
Plus. the images have sized based on the overall number of them and their position in a list.
So the problem is in situation when I have 1 big square (takes position of 4 small squares) and 5 small squares around him from right and bottom.
The big one is first as he should be.
Next to him (top right corner) is second one, that's also correct.
The third one is in bottom left corner, and it should be in the second line and on the far right. Because of this one, all the others are in wrong position, so the last one is overflowing.
I've tried a lot of value combinations for justify-content, align-content, align-items and align-self but nothing have worked.
I'll go back to ton of classes and position absolute solution, if there is no flex solution for this. But I don't like it. It's too much styles and it doesn't look good.
Any advice would be greatly appreciated.
I think float is a better option for you, check out this snippet:
.grid {
width: 300px;
}
.box {
background: orange;
width: 90px;
height: 90px;
margin: 5px;
float: left;
}
.wide {
width: 190px;
}
.tall {
height: 190px;
}
.empty {
background: transparent
}
/* you can ignore everything after this comment - it's all for illustration */
body {
background: #334;
color: white;
font-family: sans-serif;
}
.example {
display: inline-block;
margin: 5px;
border: 1px solid #445;
padding: 10px;
width: 300px;
}
h3 {
margin: 0 0 5px 0;
}
<div class="example">
<h3>Example 1</h3>
<div class="grid">
<div class="box wide tall"></div>
<div class="box tall empty"></div>
<div class="box wide empty"></div>
<div class="box"></div>
</div>
</div>
<div class="example">
<h3>Example 2</h3>
<div class="grid">
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
</div>
<div class="example">
<h3>Example 4</h3>
<div class="grid">
<div class="box wide tall"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
</div>
Flex is still trying to make complete rows of elements, so your big square and your little square are part of one row; there's no support for stacking beyond that.
Float on the other hand tries to stuff elements wherever it can fit them.
EDIT
I've updated this answer with examples on how to reproduce most of the images above (I've purposefully left out the 2 by 2 example - didn't want to cloud the answer with classes for boxes of 1.5 height/width).
Use of an empty class to remove color from blocks, as well as classes tall and wide to fill in spots of all sizes should help you customize your layout however you see fit. One note - here empty sets the background color to transparent. Your empty class may do more or less than this. You may not even need an empty class if all it is is a div without content.
There is no way to handle this layout with flex in a single container.
You need to do a little trick to achieve it.
The easier one would be to take the third item out of the flex layout, positioning it absolute:
.grid {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-content: space-between;
width: 300px;
height: 300px;
position: relative;
}
.item {
background-color: lightblue;
width: 100px;
height: 100px;
margin: 0px;
border: transparent solid 5px;
box-sizing: border-box;
background-clip: content-box;
}
.item:first-child {
width: 200px;
height: 200px;
}
.item:nth-child(2) {
background-color: blue;
position: absolute;
top: 100px;
right: 0px;
}
<div class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
Another posibility, may be more in the flex idea, but also tricky
Set the big element with a margin-bottom negative, that makes it occupy only 1 row (being the height of a row the size of the small boxes).
Now be have a layout with 3 rows. The problem will be that the 3rd box will be under the first, big box. To solve this, we are setting a pseudo element (I have styled the snippet to make it visible, in production just set it to height 0 and it will disappear) with the same properties of width and margin of the first element.
.grid {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-content: space-between;
width: 300px;
height: 300px;
position: relative;
}
.grid:after {
content: "";
order: 3;
background-color: red;
width: 190px;
height: 10px;
margin: 5px;
}
.item {
background-color: lightblue;
width: 90px;
height: 90px;
margin: 5px;
}
.item:first-child {
width: 190px;
height: 190px;
margin-bottom: -100px;
order: 1;
opacity: 0.5;
}
.item:nth-child(2) {
order: 2;
}
.item:nth-child(n+3) {
order: 4;
}
<div class="grid">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
</div>

Is there any use for flex-shrink when flex-wrap is wrap?

I don't think flex-shrink and flex-wrap:wrap; make sense together but I wonder if there is something I'm missing.
.container{
background: #DDD;
width: 300px;
height: 100px;
padding: 20px;
display: flex;
flex-direction: row;
flex-wrap: nowrap
}
.tags{
background: orange;
box-sizing: border-box;
flex: 1 3 200px;
}
.redes{
background: cyan;
box-sizing: border-box;
flex: 0 1 200px;
}
.wrap{
flex-wrap: wrap;
}
<div class="container">
<div class="tags">box1</div>
<div class="redes">box2</div>
</div>
<div class="container wrap">
<div class="tags">box1</div>
<div class="redes">box2</div>
</div>
I understand that when, flex-wrap is set to nowrap, the negative space gets distributed using the values on flex-shrink. Meanwhile, if flex-wrap is set to wrap, there can't be any negative space, can it? Therefor this property is just useless, or at least I can see any effect. Is this right?
Meanwhile, if flex-wrap is set to wrap, there can't be any negative space, can it?
If an element is wider than the flex container, it can't wrap across multiple lines, but it can shrink.
Therefor this property is just useless, or at least I can see any effect. Is this right?
Nope, you'll see the effect when a flex item would otherwise overflow its parent container.
.box {
background-color: pink;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.wide {
background-color: lightgreen;
flex: 0 0 auto;
margin: 10px 0;
width: 150%;
}
.shrink {
background-color: lightblue;
flex-shrink: 1;
}
<div class="box">
<div class="wide shrink">
Wide, shrinks
</div>
<div class="wide">
Wide, won't shrink
</div>
</div>

Why does space-around allow flex items to overflow on the left side?

It seems that Chrome doesn't handle justify-content: space-around correctly when the content overflows the flex container, and the container is not set up to allow wrapping, but instead horizontal scrolling.
Some of the content overflows on the left side of the flex container, and is cut off. I want it to overflow on the right side, so that I can reach it by scrolling horizontally.
Here is an example:
#container {
display: flex;
width: 500px;
justify-content: space-around;
border: solid black;
overflow: auto;
}
.item {
min-width: 200px;
margin: 10px;
background-color: red;
display: table;
font-size: 48pt;
text-align: center;
}
<div id="container">
<div class="item">1</div><div class="item">2</div>
<div class="item">3</div><div class="item">4</div>
<div class="item">5</div><div class="item">6</div>
</div>
That's because when there isn't enough space, space-around behaves like center:
If the leftover free-space is negative or there is only a single flex
item on the line, this value is identical to center.
And center behaves like you describe:
If the leftover free-space is negative, the flex items will overflow
equally in both directions.
Instead, you can use space-between:
If the leftover free-space is negative or there is only a single flex
item on the line, this value is identical to flex-start.
Of course, then you won't have half-size spaces on neither end of the flex line. You can insert pseudo-element to have full-size spaces, though.
#container {
display: flex;
justify-content: space-between; /* Instead of space-around */
}
#container::before, #container::after {
content: ''; /* Insert space before the first item and after the last one */
}
.container {
display: flex;
width: 500px;
border: solid black;
justify-content: space-between;
overflow: auto;
margin: 10px 0;
}
.container::before, .container::after {
content: '';
}
.item {
margin: 10px;
background-color: red;
display: table;
font-size: 48pt;
text-align: center;
}
.big > .item {
min-width: 200px;
}
<div class="container big">
<div class="item">1</div><div class="item">2</div>
<div class="item">3</div><div class="item">4</div>
<div class="item">5</div><div class="item">6</div>
</div>
<div class="container">
<div class="item">1</div><div class="item">2</div>
<div class="item">3</div><div class="item">4</div>
<div class="item">5</div><div class="item">6</div>
</div>
Since the container is limited in width and you want overflowing flex items to be accessed via horizontal scrolling, why use justify-content: space-around?
Try justify-content: flex-start:
Revised Codepen
To understand why overflowing flex items may be inaccessible via scroll, see this answer.
If you're interested in a Javascript workaround for the original code, see this post:
When centering horizontally, li's get cut off

Resources