How to get flex items to grow according to content until both overflow

Viewed 54

I have a flex container with 2 items that have text of varying sizes. The design I need to make work has a few constraints:

  • If both items fit in the container without having to truncate, both take up as much space as the content needs
  • If one takes up more than 50% the space but the other doesn't, the larger one can take up 50% + the remaining space available (so, it should flex-grow as needed, while the other one uses the content's width)
  • If both take up more than 50%, they should equally have 50% width

The problem with flex is that, in the final case, it seems to have each item flex-grow at different proportions (despite them each having flex-grow 1). If one item has longer text than the other, it takes up more space, despite them both needing more space than 50%.

I tried to recreate this problem here:

.container {
    display: flex;
    flex-wrap: nowrap;
    width: 600px;
    margin: 0px auto;
    overflow: hidden;
    background-color: #BDBDBD;
}

.label {
  font-size: 1.2rem;
  text-align: center;
  padding: 20px;
  box-sizing: border-box;
}

.child {
    position: relative;
    width: auto;
    margin: 0.4em;
    border-radius: 1em;
    background-color: lightblue;
    padding: 10px;
    box-sizing: border-box;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: auto;
    min-width: auto;
    width: auto;
    max-width: 100%;
}

/* Hard-coded */

.problem {
  border: 2px solid red;
}

.equal {
  width: 50%;
  border: 2px solid green;
}

.no-shrink {
  flex-shrink: 0;
  border: 2px solid green;
}
<div class="container">
    <div class="child problem">9 long long long long long long long long long</div>
    <div class="child problem">15 long long long long long long long long long long long long long long long</div>
</div>
<div class="container">
    <div class="child">12 long long long long long long long long long long long long</div>
    <div class="child">12 long long long long long long long long long long long long</div>
</div>

<div class="container">
    <div class="child">12 long long long long long long long long long long long long</div>
    <div class="child problem">7 long long long long long long long</div>
</div>

<div class="container">
    <div class="child red-child">2 long long</div>
    <div class="child green-child">12 long long long long long long long long long long long long</div>
</div>

<!-- The examples below are only to show what I'd like the flex items to do visually -->
<div class="container label">Should look like:</div>

<div class="container">
    <div class="child equal">9 long long long long long long long long long</div>
    <div class="child equal">15 long long long long long long long long long long long long long long long</div>
</div>
<div class="container">
    <div class="child">12 long long long long long long long long long long long long</div>
    <div class="child">12 long long long long long long long long long long long long</div>
</div>

<div class="container">
    <div class="child">12 long long long long long long long long long long long long</div>
    <div class="child no-shrink">7 long long long long long long long</div>
</div>

<div class="container">
    <div class="child">2 long long</div>
    <div class="child">12 long long long long long long long long long long long long</div>
</div>

The numbers before the "long"s indicate how many times the word "long" appears, hence how long that text is. As you can see in the red-outlined divs, the longer text gets to take up more room. I hardcoded how I need it to look below -- the "fixed" versions are outlined in green.

Is flex capable of this? Or do I need to basically use javascript to solve this?

1 Answers

In order for this to work, each child element needs a max-width style applied to it based on the total width of both child elements. This can be accomplished with javascript like the attached example.

In this example, if both child elements are greater than 50% of the container, a ".max-width-50" class is applied to both elements. If only one of the elements is greater than 50%, a ".max-width-100" class is applied to only that element in the row.

In order to properly calculate each element's width, overflow: hidden has to be applied after the javascript calculations have completed. Therefore, a .overflow class is applied to all elements that need it after the calculations have finished.

let containers = document.querySelectorAll('.container');

containers.forEach(container => {
  let children = container.querySelectorAll('.child');
  let counter = 0;
  let widthCounter = 0;

  containerWidth = container.offsetWidth;

  if (children.length) {
    children.forEach(child => {
      let childWidth = child.offsetWidth;
      let overMax = childWidth / containerWidth;


      if (overMax > 0.50) {
        child.classList.add('max-width-100');
        counter++;
      }

      widthCounter += childWidth;
    });

    // If both elements are greater than 50%
    if (counter == 2) {
      children.forEach(child => {
        child.classList.add('max-width-50');
        child.classList.remove('max-width-100');
      })
    }

    // If elements total width is greater than 100%
    if (widthCounter > 1.00) {
      children.forEach(child => {
        child.classList.add('overflow-hidden');
      });
    }

  }
});
.container {
  display: flex;
  flex-wrap: nowrap;
  width: 600px;
  margin: 0px auto;
  overflow: hidden;
  background-color: #BDBDBD;
}

.label {
  font-size: 1.2rem;
  text-align: center;
  padding: 20px;
  box-sizing: border-box;
}

.child {
  position: relative;
  width: auto;
  margin: 0.4em;
  border-radius: 1em;
  background-color: lightblue;
  padding: 10px;
  box-sizing: border-box;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex-grow: 1;
  flex-shrink: 1;
  flex-basis: auto;
  max-width: 100%;
}

.max-width-50 {
  flex-basis: 50%;
  max-width: 50%;
}

.max-width-100 {
  max-width: 100%;
}

.overflow-hidden {
  overflow: hidden;
}


/* Hard-coded */

.problem {
  border: 2px solid red;
}

.equal {
  width: 50%;
  border: 2px solid green;
}

.no-shrink {
  flex-shrink: 0;
  border: 2px solid green;
}
<div class="container">
  <div class="child problem">9 long long long long long long long long long</div>
  <div class="child problem">15 long long long long long long long long long long long long long long long</div>
</div>
<div class="container">
  <div class="child">12 long long long long long long long long long long long long</div>
  <div class="child">12 long long long long long long long long long long long long</div>
</div>

<div class="container">
  <div class="child">12 long long long long long long long long long long long long</div>
  <div class="child problem">7 long long long long long long long</div>
</div>

<div class="container">
  <div class="child red-child">2 long long</div>
  <div class="child green-child">12 long long long long long long long long long long long long</div>
</div>

<!-- The examples below are only to show what I'd like the flex items to do visually -->
<div class="container label">Should look like:</div>

<div class="container">
  <div class="child equal">9 long long long long long long long long long</div>
  <div class="child equal">15 long long long long long long long long long long long long long long long</div>
</div>
<div class="container">
  <div class="child">12 long long long long long long long long long long long long</div>
  <div class="child">12 long long long long long long long long long long long long</div>
</div>

<div class="container">
  <div class="child">12 long long long long long long long long long long long long</div>
  <div class="child no-shrink">7 long long long long long long long</div>
</div>

<div class="container">
  <div class="child">2 long long</div>
  <div class="child">12 long long long long long long long long long long long long</div>
</div>

Related