Search code examples
htmlcsscss-animations

Added content to fixed height make it not viewable, and using null height break animation


I have a button that should hidden area with nice animation. It's using height attribute to make the animation.

The issue is, when I add content to the "hiddenable" area, the content isn't viewable as it's outside the given height. And I have to clic again on "Open" to re-hide and re-open the area.

Here is a MRE :

function changeVisility(all, toCancel = null) {
    if(toCancel != null)
        toCancel.preventDefault();
    if(!(Symbol.iterator in Object(all)))
        all = [ all ];
    for(var div of all) {
        for(const wrapper of div.querySelectorAll(".collapse-wrapper")) {
            if(wrapper.style.height == '0px') {
                wrapper.style.height = wrapper.querySelector(".collapse-content").getBoundingClientRect().height + `px`;
            } else {
                wrapper.style.height = '0px';
            }
        }
    }
}

for(const wrapper of document.querySelectorAll(".collapse-wrapper")) {
    if(wrapper.style.height != '0px') { // set real height
        wrapper.style.height = wrapper.querySelector(".collapse-content").getBoundingClientRect().height + `px`;
    } else
        wrapper.style.height = '0px';
}

function addContent() {
    document.getElementById("contentToAdd").insertAdjacentHTML("beforeend", "<p>Other text</p>");
}
.collapse-wrapper {
    overflow: hidden;
    transition: height 300ms ease-in;
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">

<body style="margin-left: 10px;">
<div class="form-check" style="padding-top: 0.5rem;">
    <input class="form-check-input" type="checkbox" name="newinput" id="newinput" onchange="changeVisility(document.getElementById('input_selection'));">
    <label class="form-check-label" for="newinput">
        Open
    </label>
</div>
<div id="input_selection">
    <div style="height: 0px;" class="collapse-wrapper">
        <div class="collapse-content row" id="contentToAdd" style="background-color: red;">
            <button onclick="addContent()" type="button" class="btn btn-primary">Add</button>
            <p>Some text</p>
        </div>
    </div>
</div>
</body>

To reproduce :

  1. Clic on "Open" to open the area
  2. Clic on "Add" - Nothing show (but the text is well added to HTML)
  3. Clic on "Open" twice (to hide then re-open)
  4. See the red area upgrade with "Other text" showed

How can I make them be showable and keeping the nice animation ?

I tried to set height to null to show, but there is no longer animation...


Solution

  • Everytime content is added you need to recalculate the new height. addContent(e) has been changed:

    function addContent(e) {
      this.parentElement.insertAdjacentHTML("beforeend", "<p>Other text</p>");
      const wrapper = this.closest(".collapse-wrapper");
      wrapper.style.height = wrapper.querySelector(".collapse-content")
        .getBoundingClientRect().height + `px`;
    }
    

    In fact I changed everything so it's no longer dependent on #ids thus allowing you to add as many dropdowns as you like. Every <input> needs class="toggle" and every <button> needs class="add" (See Example 1). BTW the animated dropdown can be done with CSS and no JavaScript at all (see Example 2 and this article).

    Example 1

    function changeVisility(e) {
      const wrapper = this.closest(".form-check").nextElementSibling.querySelector(".collapse-wrapper");
      if (wrapper.style.height == '0px') {
        wrapper.style.height = wrapper.querySelector(".collapse-content").getBoundingClientRect().height + `px`;
      } else {
        wrapper.style.height = '0px';
      }
    }
    
    function addContent(e) {
      this.parentElement.insertAdjacentHTML("beforeend", "<p>Other text</p>");
      const wrapper = this.closest(".collapse-wrapper");
      wrapper.style.height = wrapper.querySelector(".collapse-content").getBoundingClientRect().height + `px`;
    }
    
    document.querySelectorAll(".toggle").forEach(t => t.onchange = changeVisility);
    
    document.querySelectorAll(".add").forEach(a => a.onclick = addContent);
    .collapse-wrapper {
      overflow: hidden;
      transition: height 300ms ease-in;
    }
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    
    <body style="margin-left: 10px;">
      <div class="form-check" style="padding-top: 0.5rem;">
        <label class="form-check-label">
          <input class="toggle form-check-input" type="checkbox" name="newinput">
          Open
        </label>
      </div>
      <div id="input_selection">
        <div style="height: 0px;" class="collapse-wrapper">
          <div class="collapse-content row" style="background-color: red;">
            <button type="button" class="add btn btn-primary">Add</button>
            <p>Some text</p>
          </div>
        </div>
      </div>
    </body>

    Example 2

    function addContent(e) {
      const wrapper = this.closest(".collapse-wrapper");
      wrapper.querySelector(".collapse-content").insertAdjacentHTML("beforeend", `<p>More text</p>`);
    }
    
    document.querySelectorAll(".add").forEach(a => a.onclick = addContent);
    .toggle {
      display: none;
    }
    
    .collapse-wrapper {
      height: auto;
      max-height: 0px;
      overflow: hidden;
      transition: max-height 0.7s ease-in;
    }
    
    .toggle:checked~.collapse-wrapper {
      max-height: 100vh;
    }
    
    .form-check-label {
      display: block;
      width: 90%;
      padding: 0 0 1ex 1ch;
      text-align: center;
      cursor: pointer;
    }
    
    .form-check-label::before {
      content: "Open";
    }
    
    .toggle:checked+.form-check .form-check-label::before {
      content: "Close";
    }
    
    .btn-block {
      width: 100%
    }
    
    p {
      padding: 1ex 2ch;
    }
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    
    <body>
      <main class="container ml-1">
        <section class="row">
          <input id="t1" class="toggle" type="checkbox">
          <div class="form-check col-12" style="padding-top: 0.5rem;">
            <label for="t1" class="form-check-label"></label>
          </div>
          <div class="collapse-wrapper row">
            <div class="collapse-content bg-light col-12">
              <button class="add btn btn-block btn-primary" type="button">Add</button>
              <p>Some text</p>
            </div>
          </div>
        </section>
      </main>
    </body>