skip to Main Content

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>

2

Answers


  1. Chosen as BEST ANSWER

    Please see the answer by Fabrizio Calderan for arguably the correct solution.

    For sake of completeness though, this is closer to what I originally had in mind: using clip-path instead of max-height and overflow. This avoids the scrollbar appearing unnecessarily and vanishing instantly while transitioning - if the final content is less than the end height / max-height.

    :root {
      --max-height: 150px;
    }
    
    button {
      padding: 1rem;
      background: #2aabd2;
    }
    
    #list-id {
      transition: clip-path 2s linear;
      position: fixed;
      clip-path: rect(auto auto 0 auto);
    }
    
    #list-id.show {
      clip-path: rect(auto auto min(100%, var(--max-height, 0)) auto);
    }
    
    ul {
      margin: 0;
      padding: 0;
      list-style: none;
      max-height: var(--max-height, 0);
      overflow: auto;
      background-color: green;
    }
    
    li {
      display: block;
      background-color: red;
      padding: 0.5rem 0.75rem
    }
    <button onclick="document.getElementById('list-id')?.classList?.toggle('show')">Show / Hide</button>
    <div id="list-id">
      <ul>
        <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>
      </ul>
    </div>


  2. 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>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search