Search code examples
javascripthtmlcssgrid

How to Achieve Smooth Expansion and Prevent Grid Row Gaps with Hidden Elements in CSS


I'm working on a web layout where I have a section containing visible and hidden content. I've implemented a button to expand the hidden content smoothly using a transition effect. However, I'm facing two main issues:

While the expandable functionality works, the transition effect isn't as smooth as I'd like it to be. I've applied a CSS transition to change the grid-template-rows property, but it's not providing the desired smoothness. How can I achieve a smoother transition effect when expanding the hidden content?

Grid Row-Gap Reservation: I'm using a grid layout for the hidden content, and even when elements are hidden, the row-gap seems to be reserving extra space for them, causing a visual gap in the layout. I want to prevent this row-gap reservation for hidden elements. How can I modify my CSS to ensure that the row-gap is only applied to visible elements and not to hidden ones?

Here's a simplified version of my HTML, CSS, and JavaScript code: https://codepen.io/korneliuszburian/pen/LYaqyvN

document.addEventListener("DOMContentLoaded", () => {
  const button = document.querySelector(".btn__expand");
  let currentIndex = 0;
  let hiddenGroups = document.querySelectorAll(".cards--hidden");

  button.addEventListener("click", function() {
    if (currentIndex < hiddenGroups.length) {
      hiddenGroups[currentIndex].classList.remove("cards--hidden");
      hiddenGroups[currentIndex].classList.add("cards--active");
      currentIndex++;
    }

    if (currentIndex >= hiddenGroups.length) {
      button.style.display = "none";
    }
  });
});
body {
  background-color: #f0f0f0;
  font-family: Arial, sans-serif;
  padding: 20px;
}

.visible,
.cards--hidden {
  margin-bottom: 20px;
  padding: 15px;
  background-color: #fff;
  border: 1px solid #000;
  box-shadow: 5px 5px 0px #000;
}

.wrapper {
  border: 2px solid #000;
  padding: 10px;
  min-height: 0;
  overflow: hidden;
}

.items {
  margin-bottom: 10px;
  padding: 10px;
  background-color: #e0e0e0;
  border: 1px solid #000;
}

.item__title h2 {
  font-size: 20px;
  margin: 0 0 5px 0;
  color: #000;
}

.item__title p {
  font-size: 16px;
  margin: 0;
  color: #333;
}

.cards--hidden {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 300ms ease-in-out;
  overflow: hidden;
}

.cards--active {
  grid-template-rows: 1fr;
}

.btn__expand {
  cursor: pointer;
}
<section>
  <div class="visible">
    <div class="wrapper">
      <div class="items">
        <div class="item__title">
          <h2>Visible Item 1 Title</h2>
          <p>Description for Visible Item 1</p>
        </div>
      </div>
      <div class="items">
        <h2>Visible Item 2 Title</h2>
        <p>Description for Visible Item 2</p>
      </div>
      <div class="items">
        <h2>Visible Item 3 Title</h2>
        <p>Description for Visible Item 3</p>
      </div>
    </div>
  </div>
  <div class="cards cards--hidden">
    <div class="wrapper">
      <div class="items">
        <div class="item__title">
          <h2>Hidden Item 1 Title</h2>
          <p>Description for Hidden Item 1</p>
        </div>
      </div>
      <div class="items">
        <h2>Hidden Item 2 Title</h2>
        <p>Description for Hidden Item 2</p>
      </div>
      <div class="items">
        <h2>Hidden Item 3 Title</h2>
        <p>Description for Hidden Item 3</p>
      </div>
    </div>
  </div>
  <div class="cards cards--hidden">
    <div class="wrapper">
      <div class="items">
        <div class="item__title">
          <h2>Hidden Item 1 Title</h2>
          <p>Description for Hidden Item 1</p>
        </div>
      </div>
      <div class="items">
        <h2>Hidden Item 2 Title</h2>
        <p>Description for Hidden Item 2</p>
      </div>
      <div class="items">
        <h2>Hidden Item 3 Title</h2>
        <p>Description for Hidden Item 3</p>
      </div>
    </div>
  </div>
  <div class="btn__expand">
    <span class="btn__label">Expand all data</span>
  </div>
</section>


Solution

  • You only added display: grid to .cards--hidden, while it changes to .cards--visible, the container becomes block, so grid-template-rows: 1fr losts its effect and its transition is also lost.

    You could move these rules to the general selector .cards, to allow the transition work as intended

    .cards {
      display: grid;
      transition: grid-template-rows 300ms ease-in-out;
    }
    

    document.addEventListener("DOMContentLoaded", () => {
      const button = document.querySelector(".btn__expand");
      let currentIndex = 0;
      let hiddenGroups = document.querySelectorAll(".cards--hidden");
    
      button.addEventListener("click", function() {
        if (currentIndex < hiddenGroups.length) {
          hiddenGroups[currentIndex].classList.remove("cards--hidden");
          hiddenGroups[currentIndex].classList.add("cards--active");
          currentIndex++;
        }
    
        if (currentIndex >= hiddenGroups.length) {
          button.style.display = "none";
        }
      });
    });
    body {
      background-color: #f0f0f0;
      font-family: Arial, sans-serif;
      padding: 20px;
    }
    
    .visible,
    .cards--hidden {
      padding: 15px;
      background-color: #fff;
      border: 1px solid #000;
      box-shadow: 5px 5px 0px #000;
    }
    
    .wrapper {
      border: 2px solid #000;
      padding: 10px;
      min-height: 0;
      overflow: hidden;
    }
    
    .items {
      margin-bottom: 10px;
      padding: 10px;
      background-color: #e0e0e0;
      border: 1px solid #000;
    }
    
    .item__title h2 {
      font-size: 20px;
      margin: 0 0 5px 0;
      color: #000;
    }
    
    .item__title p {
      font-size: 16px;
      margin: 0;
      color: #333;
    }
    
    .cards {
      display: grid;
      transition: grid-template-rows 300ms ease-in-out;
    }
    
    .cards--hidden {
      grid-template-rows: 0fr;
      overflow: hidden;
    }
    
    .btn__expand {
      cursor: pointer;
      margin-top: 20px;
    }
    
    /* hide the wrapper content */
    .cards--hidden .wrapper {
      padding: 0;
      border: none;
    }
    
    /* add gap to visible items */
    .visible, .cards--active {
      grid-template-rows: 1fr;
      margin-bottom: 20px;
    }
    <section>
      <div class="visible">
        <div class="wrapper">
          <div class="items">
            <div class="item__title">
              <h2>Visible Item 1 Title</h2>
              <p>Description for Visible Item 1</p>
            </div>
          </div>
          <div class="items">
            <h2>Visible Item 2 Title</h2>
            <p>Description for Visible Item 2</p>
          </div>
          <div class="items">
            <h2>Visible Item 3 Title</h2>
            <p>Description for Visible Item 3</p>
          </div>
        </div>
      </div>
      <div class="cards cards--hidden">
        <div class="wrapper">
          <div class="items">
            <div class="item__title">
              <h2>Hidden Item 1 Title</h2>
              <p>Description for Hidden Item 1</p>
            </div>
          </div>
          <div class="items">
            <h2>Hidden Item 2 Title</h2>
            <p>Description for Hidden Item 2</p>
          </div>
          <div class="items">
            <h2>Hidden Item 3 Title</h2>
            <p>Description for Hidden Item 3</p>
          </div>
        </div>
      </div>
      <div class="cards cards--hidden">
        <div class="wrapper">
          <div class="items">
            <div class="item__title">
              <h2>Hidden Item 1 Title</h2>
              <p>Description for Hidden Item 1</p>
            </div>
          </div>
          <div class="items">
            <h2>Hidden Item 2 Title</h2>
            <p>Description for Hidden Item 2</p>
          </div>
          <div class="items">
            <h2>Hidden Item 3 Title</h2>
            <p>Description for Hidden Item 3</p>
          </div>
        </div>
      </div>
      <div class="btn__expand">
        <span class="btn__label">Expand all data</span>
      </div>
    </section>