htmlcsscss-grid

CSS grid gallery with 2 rows and automatic number of columns


I'm building a site with multiple image galleries, each with a different number of images. What I wanted to do is to set a fixed number of rows(2, in this case) and it auto generates the amount of columns so that all images fit within the grid dynamically.

I was able to achieve something similar, but it made the images display out of order, and I really wanted them to be in the order I put in the HTML file.

Here's what I was able to do:

.gallery {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: auto auto;
  grid-gap: 5px;
  width: 100%;
}

.gallery img {
  height: 180px;
  width: 100%;
  background-color: chartreuse;
}
<div class="gallery">
  <img src="images/img1.png"/>
  <img src="images/img2.png"/>
  <img src="images/img3.png"/>
  <img src="images/img4.png"/>
  <img src="images/img5.png"/>
  <img src="images/img6.png"/>
  <img src="images/img7.png"/>
  <img src="images/img8.png"/>
</div>

<div class="gallery">
  <img src="images/img1.png"/>
  <img src="images/img2.png"/>
  <img src="images/img3.png"/>
  <img src="images/img4.png"/>
  <img src="images/img5.png"/>
  <img src="images/img6.png"/>
</div>

The only other way I could thing of is multiple galleries (like .gallery-4cols and .gallery-3cols) but if it can be done with only one, it would be preferable. Also, I'd like to achieve this without any JavaScript. Thanks :^)


Solution

  • :has(:nth-last-child(n))

    Assuming that:

    There's no need to hard code anything in HTML and the pattern is simple to follow. There's basically 3 simple steps:

    1. First, add a custom property to :root:

      --qty: 1;
      

      --qty will be the number of columns (images) per row.

    2. Next, a series of rulesets that progressively increase to a predetermined limit of images. The following ruleset declares the following:
      "If .gallery :has() at least 3 children then custom property --qty is 2"

      .gallery:has(:nth-last-child(3)) {
        --qty: 2;
      }
      

      Start with 3, end with (limit - 1), and increase by 2 (ex. 3, 5, 7, ...). Increase --qty by 1.

    3. Finally, insure that .gallery has the these two property/values (lines):

      display: grid;
      grid-template-columns: repeat(var(--qty), 1fr);
      

      I recommend that you add: max-width: 100vw; to .gallery as well so that no matter what the viewport is each .gallery will span from edge to edge with no cutoff, plus each .gallery will be very responsive.

    The following example covers for 1 to 12 images per .gallery. If any .gallery has 13 or more images, it will create more rows to accommodate. Continue the pattern in step #2 if more than two rows is undesirable.

    Note: In the example, each <img> is wrapped in a <figure> which is purely optional.

    Update: An additional example with JavaScript was added.

    CSS

    /*
    || Step #1
    */
    :root {
      --qty: 1;
    }
    
    /*
    || Step #2
    || The next 5 rulesets
    */
    .gallery:has(:nth-last-child(3)) {
      --qty: 2;
    }
    
    .gallery:has(:nth-last-child(5)) {
      --qty: 3;
    }
    
    .gallery:has(:nth-last-child(7)) {
      --qty: 4;
    }
    
    .gallery:has(:nth-last-child(9)) {
      --qty: 5;
    }
    
    .gallery:has(:nth-last-child(11)) {
      --qty: 6;
    }
    
    /*
    || Step #3
    || The first two property/values (lines)
    */
    .gallery {
      display: grid; 
      grid-template-columns: repeat(var(--qty), 1fr);
      gap: 0.5vw;
      max-width: 100vw; /* Recommended */
      margin: 1vw auto;
      padding: 1vw;
      background: magenta;
    }
    
    .gallery figure {
      display: flex;
      justify-items: center;
      height: 180px;
      margin: 0;
      background: chartreuse;
    }
    
    figure img {
      width: 100%;
      /*
      || <img> isn't cutoff or distorted.
      || Change to 'cover' if you want <img> to fill
      || <figure> and get cutoff.
      */
      object-fit: contain;
      object-position: center;
    }
    <section class="gallery">
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=1">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=2">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=3">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=4">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=5">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=6">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=7">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=8">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=9">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=10">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=11">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=12">
      </figure>
    </section>
    
    <section class="gallery">
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=1">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=2">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=3">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=4">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=5">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=6">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=7">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=8">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=9">
      </figure>
    </section>
    
    <section class="gallery">
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=1">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=2">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=3">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=4">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=5">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=6">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=7">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=8">
      </figure>
    </section>
    
    <section class="gallery">
      <figure>
        <img src="https://placehold.co/320x180/000/tomato?text=1">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/tomato?text=2">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/tomato?text=3">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/tomato?text=4">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/tomato?text=5">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/tomato?text=6">
      </figure>
    </section>


    JavaScript

    This example uses JavaScript to assign CSS property --qty to each .gallery, for example:

    <section class="gallery" style="--qty: 5">
      <!-- 10 children -->
    </section>
    
    <section class="gallery" style="--qty: 8">
      <!-- 15 children -->
    </section>
    

    // Collect all .gallery into an array-like object
    const galleries = document.querySelectorAll(".gallery");
    
    // .forEach() .gallery...
    galleries.forEach(gallery => {
    
      // ...get the number of children it has...
      const kids = gallery.children.length;
      
      // ...then divide that number by 2 and round up...
      const cols = Math.ceil(kids / 2);
      
      // ...then assign that number to its --qty 
      gallery.style.setProperty("--qty", cols);
    });
    /*
    || The first two property/values (lines)
    */
    .gallery {
      display: grid;
      grid-template-columns: repeat(var(--qty), 1fr);
      gap: 0.5vw;
      max-width: 100vw; /* Recommended */
      margin: 1vw auto;
      padding: 1vw;
      background: magenta;
    }
    
    .gallery figure {
      display: flex;
      justify-items: center;
      height: 180px;
      margin: 0;
      background: chartreuse;
    }
    
    figure img {
      width: 100%;
      /*
      || <img> isn't cutoff or distorted.
      || Change to 'cover' if you want <img> to fill
      || <figure> and get cutoff.
      */
      object-fit: contain;
      object-position: center;
    }
    <section class="gallery">
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=1">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=2">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=3">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=4">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=5">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=6">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=7">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=8">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=9">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=10">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=11">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/f662d0?text=12">
      </figure>
    </section>
    
    <section class="gallery">
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=1">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=2">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=3">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=4">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=5">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=6">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=7">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=8">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/lime?text=9">
      </figure>
    </section>
    
    <section class="gallery">
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=1">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=2">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=3">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=4">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=5">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=6">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=7">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/cyan?text=8">
      </figure>
    </section>
    
    <section class="gallery">
      <figure>
        <img src="https://placehold.co/320x180/000/tomato?text=1">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/tomato?text=2">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/tomato?text=3">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/tomato?text=4">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/tomato?text=5">
      </figure>
    
      <figure>
        <img src="https://placehold.co/320x180/000/tomato?text=6">
      </figure>
    </section>