Search code examples
javascriptcsscss-transitionstoggle

CSS transitions won't happen simultaneously after classList.toggle


I've been having this issue with a transition with a ReactJS accordion. Without seeing the problem I created this snippet in vanilla and happens the seame!

const acs = document.querySelectorAll('.accordion');

acs.forEach((a, i) => {
  a.addEventListener('click', () => {
    acs.forEach((aa, ii) => {
      aa.classList.toggle('expanded', i === ii);
    })
  })
})
.accordion {
  padding: 0.5em;
  margin-bottom: 1em;
  background: #f3f3f3;
}

.accordion p:last-child {
  overflow: hidden;
  margin: 0;
  max-height: 0;
  transition: 1s linear max-height;
}

.accordion.expanded p:last-child {
  max-height: 100px;
}
<div class="accordion">
  <p>toggle me</p>
  <p>lorem ipsum</p>
</div>
<div class="accordion">
  <p>toggle me</p>
  <p>lorem ipsum</p>
</div>
<div class="accordion">
  <p>toggle me</p>
  <p>lorem ipsum</p>
</div>

Why transitions won't happen simultaneously? there's not setTimeout or transition-delay

Seems like I'm rusty with css transitions

-EDIT-

Noticed that if I apply transition on height instead it does work as expected but containers take more height than they need according to content. How can I achieve it with max-height?

const acs = document.querySelectorAll('.accordion');

acs.forEach((a, i) => {
  a.addEventListener('click', () => {
    acs.forEach((aa, ii) => {
      aa.classList.toggle('expanded', i === ii);
    })
  })
})
.accordion {
  padding: 0.5em;
  margin-bottom: 1em;
  background: #f3f3f3;
}

.accordion p:last-child {
  overflow: hidden;
  margin: 0;
  height: 0;
  transition: 1s linear height;
  outline: 1px dashed red;
}

.accordion.expanded p:last-child {
  height: 1em;
}
<div class="accordion">
  <p>toggle me</p>
  <p>lorem ipsum</p>
</div>
<div class="accordion">
  <p>toggle me</p>
  <p>lorem ipsum</p>
</div>
<div class="accordion">
  <p>toggle me</p>
  <p>lorem ipsum</p>
</div>


Solution

  • I think it is working - i.e. both transitions taking place at the same time - but you don't notice the beginning part of it transitioning down from 100px because there's nothing to show there (or rather, nothing to make not show). The amount showing in your example is not as big as 100px.

    This snippet puts the max-height of 1.4em - which is enough to accommodate a line of the text - and you can see the transitions are taking place at the same time.

    In your second example of transitioning the actual height of course you start to see the shinkage straightaway.

    Here's the snippet with a reduced max-height as a demo:

    const acs = document.querySelectorAll('.accordion');
    
    acs.forEach((a, i) => {
      a.addEventListener('click', () => {
        acs.forEach((aa, ii) => {
          aa.classList.toggle('expanded', i === ii);
        })
      })
    })
    .accordion {
      padding: 0.5em;
      margin-bottom: 1em;
      background: #f3f3f3;
    }
    
    .accordion p:last-child {
      overflow: hidden;
      margin: 0;
      max-height: 0;
      transition: 1s linear max-height;
    }
    
    .accordion.expanded p:last-child {
      max-height: 1.4em;
    }
    <div class="accordion">
      <p>toggle me</p>
      <p>lorem ipsum</p>
    </div>
    <div class="accordion">
      <p>toggle me</p>
      <p>lorem ipsum</p>
    </div>
    <div class="accordion">
      <p>toggle me</p>
      <p>lorem ipsum</p>
    </div>