1
votes

Browser: Chrome

I was trying to setup a row of boxes that have an item inside of them that can expand. I'd like the items on the same row to be the same height. When the expandable items are collapsed I expect the containers to shrink accordingly. Somehow I created something where when the box expands it keeps increasing its size.

This example was extracted from an application. So the multiple layer's of divs probably wont make much sense to you. I just extracted the minimal pieces needed to reproduce the issue.

const expandButtons = document.querySelectorAll('.expand');

for (let i = 0; i < expandButtons.length; i++) {
  const button = expandButtons[i];
  button.addEventListener("click", () => toggleExpand(button));
}

function toggleExpand(el) {
  const content = el.parentElement.querySelector('.content');
  content.classList.toggle('is-expanded');
}
.row {
  display: flex;
}

.col {
  display: flex;
}

.card {
  display: flex;
  flex-direction: column;
  border: 1px solid black;
}

.wrapper {
  height: 100%;
}

.card-block {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.content {
  max-height: 0;
  overflow: auto;
  transition: max-height 500ms;
}

.content.is-expanded {
  max-height: 6em;
}

.spacer {
  display: flex;
  flex-grow: 1;
}
<div class="row">
  <div class="col">
    <div class="card">
      <div class="wrapper">
        <div class="card-block">
          <p>ASDF</p>
          <div class="content">
            <p>First</p>
            <p>Second</p>
            <p>Third</p>
          </div>
          <div class="spacer"></div>
          <button type="button" class="expand">Expand</button>
        </div>
      </div>
    </div>
  </div>
  <div class="col">
    <div class="card">
      <div class="wrapper">
        <div class="card-block">
          <div class="content">
            <p>First</p>
            <p>Second</p>
          </div>
          <div class="spacer"></div>
          <button type="button" class="expand">Expand</button>
        </div>
      </div>
    </div>
  </div>
</div>

Question

Expanding the second box increases the height and it doesn't retract back when collapsed. Expanding/Collapsing the first fixes it. Why does this happen? (I'm interested in an explanation rather than a solution)

Edit

I added the part of the example that justifies the height: 100% to avoid confusion. It is so that I can keep the buttons on the bottom. This involves adding display: flex; flex-direction: column; to .card-block and adding the .spacer class and corresponding element. These do not contribute to the problem but are the reason for the height: 100%.

BTW, and easy way to fix it is to remove flex-direction: column from .card. Again, I'm interested in the "why".

4
Are the height: 100% for .wrapper and .card-block necessary for some reason? - Itay Ganor
@ItayGanor Yeah, in the app I am pushing the button down to the bottom on all of them with a flex-grow: 1 on a spacer. I took that bit out since it wasn't part of the problem but the height: 100% was necessary to produce the issue. - bygrace
No matter why this happens, when using height: 100% on .wrapper you need to ask yourself, 100% of what? ... since its parent has not a defined height, those 100% will resolve to auto, and the .card-block will do the same. It appears to somewhat behave in Chrome, thought this won't work cross browsers. - Ason
@LGSon What do you mean that the height will resolve to auto? If I set them to auto it doesn't have the same behavior. The 100% I was going for is the calculated height of the tallest element in the row. - bygrace
I posted an answer. I don't mean auto will work, I mean that an element having height: 100% picks up those 100% from its parent, but for that to work, also the parent need a height, which your doesn't have, hence the 100% will resolve to auto - Ason

4 Answers

2
votes

Note, the odd behavior appears to be a bug in Chrome, as this does not happen in the other browsers.

No matter why this happens, when using height: 100% on .wrapper you need to ask yourself, 100% of what?

An element having height: 100% picks up those 100% from its parent, but for that to work, also the parent need a height, which your card doesn't have, hence the 100% will resolve to auto.

And since wrapper will be resolved auto, so will also the .card-block, as its parent (the wrapper) doesn't have a defined height either. (It appears to somewhat behave in Chrome, thought this won't work cross browsers.)

The solution is to use Flexbox all the way.

Stack snippet

const expandButtons = document.querySelectorAll('.expand');

for (let i = 0; i < expandButtons.length; i++) {
  const button = expandButtons[i];
  button.addEventListener("click", () => toggleExpand(button));
}

function toggleExpand(el) {
  const content = el.parentElement.querySelector('.content');
  content.classList.toggle('is-expanded');
}
.row {
  display: flex;
}

.col {
  display: flex;
}

.card {
  display: flex;
  flex-direction: column;
  border: 1px solid black;
}

.wrapper {
  /*height: 100%;                removed  */
  flex: 1;                   /*  added, fill parent height  */
  display: flex;             /*  added  */
  /*align-items: stretch         this is the default and make its item,
                                 the "card-block", fill its parent height  */
}

.card-block {
  /*height: 100%;                removed  */
  display: flex;
  flex-direction: column;
}

.content {
  max-height: 0;
  overflow: auto;
  transition: max-height 500ms;
}

.content.is-expanded {
  max-height: 6em;
}

.spacer {
  display: flex;
  flex-grow: 1;
}
<div class="row">
  <div class="col">
    <div class="card">
      <div class="wrapper">
        <div class="card-block">
          <p>ASDF</p>
          <div class="content">
            <p>First</p>
            <p>Second</p>
            <p>Third</p>
          </div>
          <div class="spacer"></div>
          <button type="button" class="expand">Expand</button>
        </div>
      </div>
    </div>
  </div>
  <div class="col">
    <div class="card">
      <div class="wrapper">
        <div class="card-block">
          <div class="content">
            <p>First</p>
            <p>Second</p>
          </div>
          <div class="spacer"></div>
          <button type="button" class="expand">Expand</button>
        </div>
      </div>
    </div>
  </div>
</div>
0
votes

You should use height: auto, rather than height: 100%;

const expandButtons = document.querySelectorAll('.expand');

expandButtons.forEach(x => {
  x.addEventListener("click", () => toggleExpand(x));
});

function toggleExpand(el) {
  const content = el.parentElement.querySelector('.content');
  content.classList.toggle('is-expanded');
}
.row {
  display: flex;
}

.col {
  display: flex;
}

.card {
  display: flex;
  flex-direction: column;
  border: 1px solid black;
}

.wrapper {
  height: auto;
}

.card-block {
  height: auto;
}

.content {
  max-height: 0;
  overflow: auto;
  transition: max-height 500ms;
}

.content.is-expanded {
  max-height: 6em;
}
<div class="row">
  <div class="col">
    <div class="card">
      <div class="wrapper">
        <div class="card-block">
          <p>ASDF</p>
          <div class="content">
            <p>First</p>
            <p>Second</p>
            <p>Third</p>
          </div>
          <button type="button" class="expand">Expand</button>
        </div>
      </div>
    </div>
  </div>
  <div class="col">
    <div class="card">
      <div class="wrapper">
        <div class="card-block">
          <div class="content">
            <p>First</p>
            <p>Second</p>
          </div>
          <button type="button" class="expand">Expand</button>
        </div>
      </div>
    </div>
  </div>
</div>
0
votes

As far as I understood you mean this. I just add align-items:baseline; to .card class. Also I remove margin-top from .card-block > p class.

const expandButtons = document.querySelectorAll('.expand');

for (let i = 0; i < expandButtons.length; i++) {
  const button = expandButtons[i];
  button.addEventListener("click", () => toggleExpand(button));
}

function toggleExpand(el) {
  const content = el.parentElement.querySelector('.content');
  content.classList.toggle('is-expanded');
}
.row {
  display: flex;
}

.col {
  display: flex;
}

.card {
  display: flex;
  flex-direction: column;
  border: 1px solid black;
  align-items: baseline;
}

.wrapper {
  height: 100%;
}

.card-block {
  height: 100%;
}

.card-block > p {
   margin-top: 0;
}

.content {
  max-height: 0;
  overflow: auto;
  transition: max-height 500ms;
}

.content.is-expanded {
  max-height: 6em;
}
<div class="row">
  <div class="col">
    <div class="card">
      <div class="wrapper">
        <div class="card-block">
          <p>ASDF</p>
          <div class="content">
            <p>First</p>
            <p>Second</p>
            <p>Third</p>
          </div>
          <button type="button" class="expand">Expand</button>
        </div>
      </div>
    </div>
  </div>
  <div class="col">
    <div class="card">
      <div class="wrapper">
        <div class="card-block">
          <div class="content">
            <p>First</p>
            <p>Second</p>
          </div>
          <button type="button" class="expand">Expand</button>
        </div>
      </div>
    </div>
  </div>
</div>
0
votes

A possible solution is to use

.wrapper {
  flex: 1;
  display: flex;
}

Rather than .wrapper {height: 100%}.

const expandButtons = document.querySelectorAll('.expand');

for (let i = 0; i < expandButtons.length; i++) {
  const button = expandButtons[i];
  button.addEventListener("click", () => toggleExpand(button));
}

function toggleExpand(el) {
  const content = el.parentElement.querySelector('.content');
  content.classList.toggle('is-expanded');
}
.row {
  display: flex;
}

.col {
  display: flex;
}

.card {
  display: flex;
  flex-direction: column;
  border: 1px solid black;
}

.wrapper {
  flex: 1;
  display: flex;
}

.card-block {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.content {
  max-height: 0;
  overflow: auto;
  transition: max-height 500ms;
}

.content.is-expanded {
  max-height: 6em;
}

.spacer {
  display: flex;
  flex-grow: 1;
}
<div class="row">
  <div class="col">
    <div class="card">
      <div class="wrapper">
        <div class="card-block">
          <p>ASDF</p>
          <div class="content">
            <p>First</p>
            <p>Second</p>
            <p>Third</p>
          </div>
          <div class="spacer"></div>
          <button type="button" class="expand">Expand</button>
        </div>
      </div>
    </div>
  </div>
  <div class="col">
    <div class="card">
      <div class="wrapper">
        <div class="card-block">
          <div class="content">
            <p>First</p>
            <p>Second</p>
          </div>
          <div class="spacer"></div>
          <button type="button" class="expand">Expand</button>
        </div>
      </div>
    </div>
  </div>
</div>

According to my understanding, height: 100% confuses the element, as its parent's height is the expanded one (after the transition), so it want to fill it. Using flex: 1; and display: flex, the element will fill the remaining height, and will be a flexbox too, meaning its child won't change the .card height too.