Search code examples
htmlcsscss-grid

Collapsible rows in grid


I am using a grid layout to show several rows. Each row contains 3 columns (an image, a label and an svg graphic) and I want (based on some logic not relevant here) to hide/unhide entire rows.

After a lot of trial and error and reading a myriad of different blog posts, I adapted the following approach which is pretty close to what I want to achieve.

There are 2 minor things which, however, bother me.

  1. Neither the <img> nor the <svg> are completely centered. .grid-row is 4px taller than necessary (it's height is 104px, while both the <img> and the <svg> are 100px tall). Why? How to get rid of the 4px? Comment from @Paulie_D solved this issue
  2. In the transition, the content and the surrounding grid do not move at teh same speed. I'd like that the whole row disappears as one unit. now, the image, text and svg seem to disappear on their own. Furthermore the last bit of the row disappears from the top, while everything else disappears from the bottom. How to change that?

function toggleRow() {
  const i = d3.select('#row').property('value');
  const row = d3.select('.grid-row:nth-child(' + i +')');
  row.classed('visible', !row.classed('visible'));
}
:root {
  --transitionLength: 0.4s;
  --transitionTiming: ease;
}

.grid {
  display: inline-grid;
  grid-template-columns: 110px minmax(200px, max-content) 1fr;
  row-gap: 5px;
}

.grid-row {
  display: grid;
  grid-template-columns: subgrid;
  grid-template-rows: 0fr;
  grid-column: 1 / 4;
  align-items: center;
  column-gap: 10px;
  overflow: hidden;
  background-color: lightgray;
  transition: grid-template-rows var(--transitionLength) var(--transitionTiming);
}

.grid > .visible {
  grid-template-rows: 1fr;
}

.grid-item {
  min-height: 0;
  transform: translateY(-100%);
  visibility: hidden;
  transition: transform var(--transitionLength) var(--transitionTiming),
    visibility 0s var(--transitionLength) var(--transitionTiming);
}

.grid > .visible > .grid-item {
  transform: translateY(0);
  visibility: visible;
  transition: transform var(--transitionLength) var(--transitionTiming),
    visibility 0s linear;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
<div class="grid">
  <div class="grid-row visible">
    <div class="grid-item img"><img src="https://picsum.photos/100" alt="Placeholder Image"/></div>
    <div class="grid-item label">
      <h2>Can be a long Line with several words...</h2>
    </div>
    <div class="grid-item svg">
      <svg xmlns="http://www.w3.org/2000/svg" width="163.63" height="100" viewBox="0 0 18 11">
        <rect width="18" height="11" fill="#fff" />
        <path d="M0,5.5h18M6.5,0v11" stroke="#002F6C" stroke-width="3" />
      </svg>
    </div>
  </div>
  <div class="grid-row">
    <div class="grid-item img"><img src="https://picsum.photos/100"  alt="Placeholder Image" /></div>
    <div class="grid-item label">
      <h2>...or very short</h2>
    </div>
    <div class="grid-item svg">
      <svg xmlns="http://www.w3.org/2000/svg" width="163.63" height="100" viewBox="0 0 18 11">
        <rect width="18" height="11" fill="#fff" />
        <path d="M0,5.5h18M6.5,0v11" stroke="#002F6C" stroke-width="3" />
      </svg>
    </div>
  </div>
</div>
<br>
<input type="number" id="row" name="number" min="1" max="3" value="1" />
<button onclick="toggleRow()">Toggle Row</button>


Solution

  • I think I over-complicated matters a bit. A simple transition (without the transform) on grid-template-rows should do the trick.

    N.B. I needed to remove the align-items rule, lest the hidden content would still protrude (for whatever reason).

    function toggleRow() {
      const i = d3.select('#row').property('value');
      const row = d3.select('.grid-row:nth-child(' + i +')');
      row.classed('visible', !row.classed('visible'));
    }
    :root {
      --transitionLength: 250ms;
      --transitionTiming: ease-out;
    }
    
    .grid {
      display: inline-grid;
      grid-template-columns: 110px minmax(200px, max-content) 1fr;
      row-gap: 5px;
    }
    
    .grid-row {
      display: grid;
      grid-column: 1/4;
      grid-template-columns: subgrid;
      background-color: lightgray;
      grid-template-rows: 0fr;
      transition: grid-template-rows var(--transitionLength) var(--transitionTiming);
      /*align-items: center;*/
      column-gap: 10px;
    }
    
    .grid > .visible {
      grid-template-rows: 1fr;
    }
    
    .grid-row > .grid-item {
      overflow: hidden;
      min-height: 0;
    }
    
    
    .grid-item > img, .grid-item > svg {
      vertical-align: middle;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
    <div class="grid">
      <div class="grid-row visible">
        <div class="grid-item img"><img src="https://picsum.photos/100" alt="Placeholder Image" /></div>
        <div class="grid-item label">
          <h2>Can be a long Line with several words...</h2>
        </div>
        <div class="grid-item svg">
          <svg xmlns="http://www.w3.org/2000/svg" width="163.63" height="100" viewBox="0 0 18 11">
            <rect width="18" height="11" fill="#fff" />
            <path d="M0,5.5h18M6.5,0v11" stroke="#002F6C" stroke-width="3" />
          </svg>
        </div>
      </div>
      <div class="grid-row">
        <div class="grid-item img"><img src="https://picsum.photos/100"  alt="Placeholder Image" /></div>
        <div class="grid-item label">
          <h2>...or very short</h2>
        </div>
        <div class="grid-item svg">
          <svg xmlns="http://www.w3.org/2000/svg" width="163.63" height="100" viewBox="0 0 18 11">
            <rect width="18" height="11" fill="#fff" />
            <path d="M0,5.5h18M6.5,0v11" stroke="#002F6C" stroke-width="3" />
          </svg>
        </div>
      </div>
    </div>
    <br>
    <div>
    <input type="number" id="row" name="number" min="1" max="3" value="1" />
    <button onclick="toggleRow()">Toggle Row</button>
    </div>