a.h.g.
a.h.g.

Reputation: 1636

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

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 class="flexer">
  <div>
     test
  </div>
  <div class="extra">
    extra
  </div>
</div>

<hr>
<div class="flexer">
  <div>
     test
  </div>
</div> - How it should look, when not hovered
<br>
<div class="flexer">
  <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.

Upvotes: 0

Views: 721

Answers (5)

kushal jain
kushal jain

Reputation: 85

<div class="flexer">
  <div>
     test
  </div>
  <div class="extra">
    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/

Upvotes: 0

a.h.g.
a.h.g.

Reputation: 1636

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 class="flexer">
  <div>
    test
  </div>
  <div class="extra">
    &nbsp;extra
  </div>
</div>

<div class="flexer">
  <div>
    test
  </div>
  <div class="extra">
    &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.

Upvotes: 0

Temani Afif
Temani Afif

Reputation: 272608

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 class="flexer">
  <div>
     test
  </div>
  <div class="extra">
    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 class="flexer">
  <div>
    test
  </div>
  <div class="extra">
    extra
  </div>
</div>

Upvotes: 1

Kameron
Kameron

Reputation: 10846

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 class="flexer">
  <div>
    test
  </div>
  <div class="extra">
    extra
  </div>
</div>

Upvotes: 1

dita lubis
dita lubis

Reputation: 184

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 class="flexer">
  <div>
     test
  </div>
  <div class="extra">
    extra
  </div>
</div>

Upvotes: 1

Related Questions