Search code examples
cssvue.jsflexboxcss-grid

How can I set up a dynamic grid based on flex or grid


I'm scratching my head on how to create this specific dynamic grid using CSS. I've been attempting it using CSS grid, but I'm curious if I should instead use Flexbox.

I'm attaching the desired adjustable layout below in a mock-up I created.

Desired dynamic layout

I'm attempting this in VueJS, and this is the code I have so far:

<script setup lang="ts">
const gridStyle = computed(() => {
  const base = 'grid-auto-rows: minmax(0, 1fr); grid-auto-flow: column;'
  if (visibleItems.value.length === 1) {
    return 'grid-template-columns: 1fr; ' + base
  } else if (visibleItems.value.length === 3) {
    return 'grid-template-columns: 4fr 1fr; ' + base
  } else {
    return 'grid-template-columns: 3fr 1fr 1fr; ' + base
  }
})

const itemStyle = (item: iFrame, index: number) => {
  const remainingItems = visibleItems.value.length - 2

  if (index === 0 && !itemIsVisible(items.value[1])) {
    return 'grid-row: span 4; grid-column: span 2; height: 100vh;'
  } else if (index < 2 && items.value.indexOf(item) < 2) {
    return 'grid-row: span 4; height: 100vh;'
  } else if (remainingItems === 1) {
    if (index === 1) {
      return `grid-row: span 1; `
    }
    if (index === 2) {
      return `grid-row: span 3; `
    }
  } else {
    const rowsToSpan = Math.min(4, Math.floor(4 / Math.max(1, remainingItems)))
    return `grid-row: span ${rowsToSpan};`
  }
}
</script>

<div class="grid h-screen gap-0" :style="gridStyle">
    <div
      v-for="(item, index) in visibleItems"
      :key="item.id"
      class="h-full w-full"
      :style="itemStyle(item, index)"
    >
</div>
</div>

This code has gotten me most of the way, but there are minor visual issues when I have two, three, four, and five items I'd love to sort out.


Solution

  • An alternative way of setting this up would be to use CSS grid-template-areas.

    That way you have simple control of the layout for each number of items.

    This snippet uses JS to find the number of items and CSS does the rest:

    <style>
      .grid {
        display: grid;
        width: 100vmin;
        aspect-ratio: 16/ 9;
        gap: 1vw;
      }
      
      .grid.n1 {
        grid-template-areas: 'A';
      }
      
      .grid.n2 {
        grid-template-areas: 'A A A A A B B';
      }
      
      .grid.n3 {
        grid-template-areas: 'A A A A A B B' 'A A A A A C C' 'A A A A A C C' 'A A A A A C C';
      }
      
      .grid.n4 {
        grid-template-areas: 'A A A B B C' 'A A A B B D' 'A A A B B D' 'A A A B B D';
      }
      
      .grid.n5 {
        grid-template-areas: 'A A A B B C' 'A A A B B D' 'A A A B B E' 'A A A B B E';
      }
      
      .grid.n6 {
        grid-template-areas: 'A A A B B C' 'A A A B B D' 'A A A B B E' 'A A A B B F';
      }
      
      .grid.n7 {
        grid-template-areas: 'A A B B C C G' 'A A B B D D G' 'A A B B E E G' 'A A B B F F G';
      }
      
      .grid.n8 {
        grid-template-areas: 'A A B B C C G' 'A A B B D D H' 'A A B B E E H' 'A A B B F F H';
      }
      
      .grid>* {
        width: 100%;
        height: 100%;
        background: gray;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      
      .grid :nth-child(1) {
        grid-area: A;
      }
      
      .grid :nth-child(2) {
        grid-area: B;
      }
      
      .grid :nth-child(3) {
        grid-area: C;
      }
      
      .grid :nth-child(4) {
        grid-area: D;
      }
      
      .grid :nth-child(5) {
        grid-area: E;
      }
      
      .grid :nth-child(6) {
        grid-area: F;
      }
      
      .grid :nth-child(7) {
        grid-area: G;
      }
      
      .grid :nth-child(8) {
        grid-area: H;
      }
    </style>
    <div class="grid">
      <div>1</div>
      <div>2</div>
      <div>3</div>
      <div>4</div>
      <div>5</div>
      <div>6</div>
      <div>7</div>
      <div>8</div>
    </div>
    <script>
      const grid = document.querySelector('.grid');
      const n = document.querySelectorAll('.grid > *').length;
      grid.classList.add('n' + n);
    </script>