Please consider the following example:
.flexer {
display: inline-flex;
border: 1px solid red;
min-width: 0;
}
.extra {
flex: 0 1 0%; /* flex-grow: 0; flex-shrink: 1; flex-basis: 0%; */
min-width: 0;
transition: flex-grow .3s cubic-bezier(.4,0,.2,1);
overflow: hidden;
}
.flexer:hover .extra {
flex-grow: 1
}
<div >
<div>
test
</div>
<div >
extra
</div>
</div>
<hr>
<div >
<div>
test
</div>
</div> - How it should look, when not hovered
<br>
<div >
<div>
test
</div>
<div>
extra
</div>
</div> - How it should look, when hovered
<br><br>
The red box should animate smoothly and precisely between the two widths (without delay).
I have trouble understanding why .flexer
(the parent) doesn't shrink when not hovered (e.g: the red box still remains full, instead of shrinking around test
).
From this q/a I understand adding min-width: 0
to the child should allow the parent to shrink. I've added it to both child and parent, to no avail.
Note 1: I'm more interested in understanding the mechanics and why this happens than finding an alternative solution (javascript, absolute positioning, etc...).
I'd like to use flexbox and I'd like to animate flex-grow
- or any other animatable flex prop - for this case, if at all possible.
Note 2: the markup is irrelevant (I'm open to changing it - e.g: adding a wrapper to any of the children, if that will make my example work).
Note 3: for a clearer understanding of the desired output, see the JS based answer I added after I realised this is not possible using only CSS.
Thanks for looking into this.
CodePudding user response:
Here is my try
.flexer {
display: inline-flex;
border: 1px solid red;
min-width: 0;
}
.extra {
flex: 0 1 0%; /* flex-grow: 0; flex-shrink: 1; flex-basis: 0%; */
width: 0;
transition: flex-grow .3s cubic-bezier(.4,0,.2,1);
overflow: hidden;
}
.flexer:hover .extra {
flex-grow: 1;
width: 100%; /* you can also use 'auto' value */
}
<div >
<div>
test
</div>
<div >
extra
</div>
</div>
CodePudding user response:
You were right on track. Just add width: 0;
to .extra
and remove the min-width
. Then set the width
for .extra
on :hover
to fit-content
or auto
.
.flexer {
display: inline-flex;
border: 1px solid red;
}
.extra {
flex: 0 1 0%;
/* flex-grow: 0; flex-shrink: 1; flex-basis: 0%; */
transition: flex-grow .3s cubic-bezier(.4, 0, .2, 1);
overflow: hidden;
width: 0;
}
.flexer:hover .extra {
flex-grow: 1;
width: fit-content;
}
<div >
<div>
test
</div>
<div >
extra
</div>
</div>
CodePudding user response:
You will not have any chance using flex-grow
or flex-basis
because by design those properties need to know the container dimension to work. they cannot update the container dimension.
What you can do is to play with width
/max-width
as you already discovered because those can affect the container dimension.
.flexer {
display: inline-flex;
border: 1px solid red;
}
.extra {
min-width: 0;
max-width: 0;
transition: .3s cubic-bezier(.4,0,.2,1);
overflow: hidden;
}
.flexer:hover .extra {
max-width: 100px;
}
<div >
<div>
test
</div>
<div >
extra
</div>
</div>
Or consider CSS grid but this will not work in all the browsers. Only Firefox for now I think:
.flexer {
display: inline-grid;
grid-template-columns: auto 0fr;
justify-content: start;
border: 1px solid red;
transition: .3s cubic-bezier(.4, 0, .2, 1);
}
.extra {
min-width: 0;
overflow: hidden;
width: 100%;
}
.flexer:hover {
grid-template-columns: auto 1fr;
}
<div >
<div>
test
</div>
<div >
extra
</div>
</div>
CodePudding user response:
From all the answers so far, I understand there's no clean CSS way to reduce the size of the parent based on computed widths of all children, while the children are being animated, using flexbox.
Technically, this means flexbox props (-shrink
, -basis
, -grow
) can't be used to animate here and (max-)width
should be used instead. I must admit, I still find it hard to believe. I would have thought flexbox got this covered.
The problem with animating max-width
or width
is you can't animate from 0
to auto
without JavaScript.
Which leads to the following solution:
[...document.querySelectorAll('.flexer')].forEach(el => {
const item = el.querySelector('.extra');
if (item) {
el.onmouseenter = () => {
item.style.maxWidth = item.scrollWidth 'px'
}
el.onmouseleave = () => {
item.style.maxWidth = '0'
}
}
})
.flexer {
display: inline-flex;
border: 1px solid red;
}
.extra {
transition: max-width .42s cubic-bezier(.4, 0, .2, 1);
overflow: hidden;
max-width: 0;
white-space: nowrap;
}
<div >
<div>
test
</div>
<div >
extra
</div>
</div>
<div >
<div>
test
</div>
<div >
I have a lot more content...
</div>
</div>
Needless to point out, if anyone has a pure CSS solution, which animates perfectly from 0
to the appropriate width for each item, I'd be happy to mark their answer as accepted.
So far, none of the provided answers has the desired output, except this one, which uses JavaScript.
CodePudding user response:
<div >
<div>
test
</div>
<div >
extra
</div>
</div>
.flexer {
display: inline-flex;
border: 1px solid red;
}
.extra {
flex: 0 1 0%;
transition: flex-grow .3s cubic-bezier(.4,0,.2,1);
overflow: hidden;
width: 0;
}
.flexer:hover .extra {
flex-grow: 1;
width: auto;
}