javascripthtmlcssscrollcss-transitions

Why does inner HTML content appear instantly after css max-height transition completes?


Using HTML and JavaScript I am trying to create a list that transitions into view when a button is clicked, including a scrollable inner container, using a max-height transition on the parent DIV.

However, while the parent div transitions smoothly, the content instantly appears after the transition completes. It seems to occur when the inner div is overflowing.

I am aware that removing the inner div, removing its overflow property or constraining its height to the parent will stop the symptoms from occuring, but I want to keep the inner div scrollable and I'm not sure why I can't.

Here is an example of what I have been attempting.

let visible = false;

function showHideList() {
  const listContainerRef = document.getElementById('list-id');
  if (!listContainerRef?.style?.maxHeight) 
    return;
    
  listContainerRef.style.maxHeight = visible ? 0 + 'px' : 200 + 'px';
  visible = !visible;
}
<button onclick="showHideList()" style="padding: 1rem; background: #2aabd2">Show / Hide</button>
<div id="list-id" style="transition: max-height 2s linear; overflow: hidden; max-height: 0;">
  <div style="overflow: auto; max-height: 200px">
    <div style="background-color: green">
      <span style="display: block; background-color: red; padding: 0.5rem 0.75rem">Item 1</span>
      <span style="display: block; background-color: red; padding: 0.5rem 0.75rem">Item 2</span>
      <span style="display: block; background-color: red; padding: 0.5rem 0.75rem">Item 3</span>
      <span style="display: block; background-color: red; padding: 0.5rem 0.75rem">Item 4</span>
      <span style="display: block; background-color: red; padding: 0.5rem 0.75rem">Item 5</span>
      <span style="display: block; background-color: red; padding: 0.5rem 0.75rem">Item 6</span>
    </div>
  </div>
</div>

Here is another example:

#list-container {
    max-height: 0;
    transition: max-height 2s ease-out;
    overflow: hidden;
}
#list-container.visible {
    max-height: 300px;
}
#list-content {
    max-height: 200px;
    overflow: auto;
}
li {
    background-color: red;
    padding: 0.5rem 0.75rem;
}
button {
    padding: 1rem;
    background: #2aabd2;
}
<button onclick="document.getElementById('list-container').classList.toggle('visible')">Show / Hide</button>
  <div id="list-container">
      <div id="list-content">
    <ul>
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
    </ul>
      </div>
</div>


Solution

  • It seems that the issue is caused by overflow: auto in the intermediate wrapper: here is a version of a scrollable list, without that wrapper and using an unordered list ul instead of a div.

    Instead of using max-height as an inline style I used a custom property (--mh) set to 0 by default in the style, so the Javascript code can be simplified a bit.

    let visible = false;
    
    /* You should set the node just once, not at every 
     * call to the showHideList() function 
     */
    const listContainerRef = document.getElementById('list-id');
    
    function showHideList() { 
      if (listContainerRef) {
        visible = !visible;
        listContainerRef.style.setProperty("--mh", +visible);        
        /* see “unary plus operator” – this converts a boolean into 0 or 1 */
      }
    }
    button {
      padding: 1rem; 
      background: #2aabd2;
      cursor: pointer;
    }
    
    #list-id {
      max-height: calc(200px * var(--mh, 0));
      transition: max-height 2s linear;
      overflow: auto;
      
      ul {
        margin: 0;
        padding: 0;
        list-style: none;
      }
    
      li {
        display: block; 
        background-color: red; 
        padding: 0.5rem 0.75rem
      }
    }
    <button onclick="showHideList()">Show / Hide</button>
    <div id="list-id">
        <ul role="list">
          <li>Item 1</li>
          <li>Item 2</li>
          <li>Item 3</li>
          <li>Item 4</li>
          <li>Item 5</li>
          <li>Item 6</li>
          <li>Item 7</li>
          <li>Item 8</li>
          <li>Item 9</li>
          <li>Item 10</li>
          <li>Item 11</li>
          <li>Item 12</li>
        </ul>
    </div>