javascripthtmlcss

Animate toggle height from 0 to auto or 100%


Please help, I want the toggle buttons to only show and hide the paragraphs but the other contents inside the div should remain always visible. Also, the CSS is the same and I am trying to smoothen the transition of the DIV's height when showing and hiding the paragraphs

const toggleButtons = document.querySelectorAll('.toggleButton');
const divs = document.querySelectorAll('div');
const paragraphs = document.querySelectorAll('.paragraph');

toggleButtons.forEach((button, index) => {
  button.addEventListener('click', () => {
    const paragraph = paragraphs[index];
    paragraph.classList.toggle('hidden');
    paragraph.classList.toggle('visible');

    if (paragraph.classList.contains('visible')) {
      divs[index].style.height = paragraph.scrollHeight + 'px';
    } else {
      divs[index].style.height = '0';
    }
  });
});
div {
  height: 0;
  overflow: hidden;
  transition: height 0.5s;
  background: blue;
}

.paragraph {
  opacity: 0;
  transition: opacity 0.5s;
}

.visible {
  opacity: 1;
  display: block;
}

.hidden {
  opacity: 0;
  display: none;
}
<button class="toggleButton">Toggle 1</button>
<div> HI
  <p class="paragraph hidden">Paragraph 1.</p>
</div>

<button class="toggleButton">Toggle 2</button>
<div> Hi
  <p class="paragraph hidden">Paragraph 2.</p>
</div>

<button class="toggleButton">Toggle 3</button>
<div> Hi
  <p class="paragraph hidden">Paragraph 3.</p>
</div>


Solution

  • While waiting for all browsers to support

    height: calc-size(auto);
    /* Currently in Canary - Experimental features */
    

    Here are several ways to toggle-animate an element's height

    Toggle height using interpolate-size: allow-keywords

    Currently supported in Chromium

    .content {
      interpolate-size: allow-keywords;
      transition: height 0.6s;
      overflow: hidden;
      height: 0;
    }
    
    button:focus + * > .content {
      height: auto;
    }
    <button type="button">Toggle 1</button>
    <div>
      <div>Short intro 1</div>
      <div class="content" id="content_1">
        1. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias labore fuga quia, iste rerum nulla, reprehenderit at quasi id omnis dolorem. Iusto repellendus nemo reiciendis rerum facere maxime dicta. Fugit.
      </div>
    </div>
    
    <button type="button">Toggle 2</button>
    <div>
      <div>Short intro 2</div>
      <div class="content" id="content_2">
        2. Lorem ipsum dolor sit ametIusto repellendus nemo reiciendis rerum facere maxime dicta. Fugit.
      </div>
    </div>
    
    <button type="button">Toggle 3</button>
    <div>
      <div>Short intro 3</div>
      <div class="content" id="content_3">
        3. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus id similique laboriosam eos perspiciatis aliquam corporis esse veniam ab aspernatur debitis, provident rerum nemo blanditiis! Beatae delectus, provident totam iusto.
      </div>
    </div>

    Toggle height using grid-template-columns, rows

    The display property cannot be transitioned (animated). Also, the trick to transition max-height is fundamentally broken since it does not work well when collapsing during a defined amount of time; makes use of magic numbers to transition to: max-height: "big-magic-number"unit;) causing animation gaps or unwanted scrollbars when using values like 100%|vh.

    Solution:

    transition (animate) the carousel element using CSS grid and grid-template-rows from 0fr to 1fr

    document.querySelectorAll('[data-toggle]').forEach(elBtn => {
      elBtn.addEventListener("click", () => {
        document.querySelector(elBtn.dataset.toggle).classList.toggle("open");
      });
    });
    .content {
      display: grid;
      grid-template-rows: 0fr;
      transition: grid-template-rows 0.5s ease-in-out;
      
      &.open {
        grid-template-rows: 1fr;
      }
      
      .content-inner {
        overflow: hidden;
      }
    }
    <button type="button" data-toggle="#content_1">Toggle 1</button>
    <div> HI
      <div class="content" id="content_1">
        <div class="content-inner">
          1. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias labore fuga quia, iste rerum nulla, reprehenderit at quasi id omnis dolorem. Iusto repellendus nemo reiciendis rerum facere maxime dicta. Fugit.
        </div>
      </div>
    </div>
    
    <button type="button" data-toggle="#content_2">Toggle 2</button>
    <div> HI
      <div class="content" id="content_2">
        <div class="content-inner">
          2. Lorem ipsum dolor sit ametIusto repellendus nemo reiciendis rerum facere maxime dicta. Fugit.
        </div>
      </div>
    </div>
    
    <button type="button" data-toggle="#content_3">Toggle 3</button>
    <div> HI
      <div class="content" id="content_3">
        <div class="content-inner">
          3. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus id similique laboriosam eos perspiciatis aliquam corporis esse veniam ab aspernatur debitis, provident rerum nemo blanditiis! Beatae delectus, provident totam iusto.
        </div>
      </div>
    </div>
    
    <hr>
    
    <button type="button" data-toggle="#content_3">I also toggle 3.</button>

    Also, don't use <p> as your carousel collapsible element. Animate display-block elements like DIV. <p> might be indeed a display-block element, but historically served the purpose as a styled content wrapper, and should be used for content and semantics, not as an architectural block, that animates.

    Accessibility alert
    For simplification-of-answer I missed to, but don't forget to add the needed A11Y Aria attributes and the needed JS code to toggle the aria-expanded="true" "false" values on click.

    Acknowledgments: