Home > Enterprise >  Flex parent won't shrink past child content, even though child has `0` computed width
Flex parent won't shrink past child content, even though child has `0` computed width

Time:06-08

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 >
    &nbsp;extra
  </div>
</div>

<div >
  <div>
    test
  </div>
  <div >
    &nbsp;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;
}

https://jsfiddle.net/1kvx2obq/1/

  • Related