My WebDev Notes: Photo gallery with CSS Grid

Habdul Hazeez - Apr 7 '20 - - Dev Community

Check the gallery online: https://ziizium.github.io/my-webdev-notes/photo-gallery/

Introduction

Almost all websites on the Internet have one form of image or another. The images can be in different extensions (png, jpg, jpeg e.t.c) and if you have a section on your website dedicated to images or to show your photography skills you'll definitely want to present them in a beautiful layout.

This experiment will show you how to achieve that or at least give you a foundation that you can build upon.

The image below is the final layout of this experiment.

An image gallery with CSS Grid

From the post title, you should know that the layout is possible thanks to CSS Grid, but CSS Grid is not supported in some browsers, therefore, it's best if we start with a progressive enhancement mindset.

Progressive enhancement

Progressive enhancement is a development approach that involves writing code that's guaranteed to run in most browsers then adding code that will work in modern browsers. Therefore, all users get the baseline experience and will only get the added functionality if their browser supports it.

The HTML for the gallery is given below. It's made up of <figure> elements which in turn contains the images in <img> tags then <figcaption> element for each image.

<div class="gallery">
    <figure class="gallery__figure large-image">
        <img class="gallery__image" src="img/usher.jpg" alt="A pciture of Usher Raymond">
        <figcaption class="gallery__caption">Usher Raymond</figcaption>
    </figure>

    <figure class="gallery__figure">
        <img class="gallery__image" src="img/full_moon.jpg" alt="A picture of the full moon">
        <figcaption class="gallery__caption">Full moon</figcaption>
    </figure>

    <figure class="gallery__figure large-image">
        <img class="gallery__image" src="img/trees.jpg" alt="A picture of trees">
        <figcaption class="gallery__caption">Trees</figcaption>
    </figure>

    <figure class="gallery__figure">
        <img class="gallery__image" src="img/bird.jpg" alt="A pictire of white birds" />
        <figcaption class="gallery__caption">Bird</figcaption>
    </figure>

    <figure class="gallery__figure large-image">
        <img class="gallery__image" src="img/manatees.jpg" alt="A picture of manatees">
        <figcaption class="gallery__caption">Manatees</figcaption>
    </figure>

    <figure class="gallery__figure">
        <img class="gallery__image" src="img/sunset.jpg" alt="A pictire of sunset" />
        <figcaption class="gallery__caption">Sunset</figcaption>
    </figure>

    <figure class="gallery__figure large-image">
        <img class="gallery__image" src="img/cat.jpg" alt="A pictire of a white cat" />
        <figcaption class="gallery__caption">Cat</figcaption>
    </figure>

    <figure class="gallery__figure">
        <img class="gallery__image" src="img/horse_unicorn.jpg" alt="A pictire of an horse unicorn" />
        <figcaption class="gallery__caption">Horse Unicorn</figcaption>
    </figure>
</div>
Enter fullscreen mode Exit fullscreen mode

You can download freely usable images from Unsplash.

The CSS is the most most important part of this experiment therefore, we'll dissect it together. The code snippet below resets the margin and padding for all elements and sets the box-sizing property to border-box.

The background color of the <body> element is also changed. The font-family is set to Trebuchet MS with back up font families and we add a little bit of padding.

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    background-color: #807d6c;
    font-family: "Trebuchet Ms",Verdana, Georgia, sans-serif;
    padding: 0.7em;
}
Enter fullscreen mode Exit fullscreen mode

When you reload your browser, you'll notice the changes and if you are using high-resolution images they will be in their default size occupying the entire viewport.

The following snippet will get us underway. We set the display property of the gallery__figure to inline-block. This will cause the images to appear on a single line and they'll fall to another line if there is no longer space. The images are also constrained to a maximum width of 300px so that we can have enough images on a single line at a larger viewport.

.gallery__figure {
    margin: 0;
    display: inline-block;
    max-width: 300px;
}
Enter fullscreen mode Exit fullscreen mode

When you reload your browser, the images will overlap with each other because we have not constrained their width to their parent container.

A crashed layout
A crashed layout in Firefox web browser

All we need is a max-width property with a value of 100 on the images.

.gallery__image {
    max-width: 100%;
}
Enter fullscreen mode Exit fullscreen mode

Refresh your browser and you'll then feel that the gallery is coming together.

An image gallery with inline-block layout
Gallery coming together

What's left is the styling for the figcaption. In our HTML file, we have a class name of gallery__caption attached to all the figcaption elements, we'll make use of this class name in our CSS.

.gallery__caption {
    padding: 0.3em 0.7em;
    background-color: rgba(0,0,0,0.5);
    color: #ffffff;
}
Enter fullscreen mode Exit fullscreen mode

The output is much better when you reload your browser.

An image gallery with styled captions
Image gallery

This will be the baseline experience for all browsers that do not support CSS Grid. If you have access to Internet Explorer the output will be the same.

In order to exclude browsers that do not support the CSS Grid, we'll make use of a feature query using the @supports rule. All we have to do is test if the browser supports the CSS Grid before applying the properties of CSS Grid.

@supports(display: grid) { /* if browser supports grid */
    /* code for browser that support CSS Grid */
}
Enter fullscreen mode Exit fullscreen mode

Now we can rearrange the images using CSS Grid.

Image layouts with CSS Grid

All code snippets from this point onwards should be inside the @supports(display: grid) {} unless otherwise stated.

The auto-fill works in conjunction with the repeat() function. The repeat() function can accept the following as its first argument:

  • An integer value or
  • The auto-fit keyword or
  • The auto-fill keyword

The second parameter to the repeat() function is the track-list which is the size of each column. Let's look at a basic example before discussing how the auto-fill works.

The following snippet will create four columns in the browser because we specify 2 as the first argument and 1fr 2fr as the second argument. This will result in 1fr 2fr 1fr 2fr giving a total of four columns.

.selector {
    display: grid;
    grid-template-columns: repeat(2, 1fr 2fr);
}
Enter fullscreen mode Exit fullscreen mode

If we don't want to explicitly specify the width of the column (like we just did) that's when we use the auto-fill keyword but with another function namely minmax().

The name of the minmax() function should give away its behavior, it accepts two comma-separated parameters. The first parameter is the lowest possible width a column can occupy. The second parameter is the highest possible width a column can occupy.

The question you might ask is: Why did we use the minmax() function when we can specify the width of the columns and leave the auto-fill to do the rest?.

The thing is when you use an auto-fill as the first parameter of the repeat() function you'll have to make use of the minmax() function to specify the minimum width and the maximum width that the browser will auto-fill on the web page.

I did not make that up, it's what the specification states about auto-fill:

When auto-fill is given as the repetition number, if the grid container has a definite size or max size in the relevant axis, then the number of repetitions is the largest possible positive integer that does not cause the grid to overflow its grid container (treating each track as its max track sizing function if that is definite or as its minimum track sizing function otherwise, and taking gap into account); if any number of repetitions would overflow, then 1 repetition. Otherwise, if the grid container has a definite min size in the relevant axis, the number of repetitions is the smallest possible positive integer that fulfills that minimum requirement. Otherwise, the specified track list repeats only once.

Therefore, the following snippet is telling the browser to auto-fill the images in the grid container with the minimum width of each column set to 200px and maximum width set to 1fr.

.selector {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
Enter fullscreen mode Exit fullscreen mode

This is what we'll use to arrange the images in the grid. Asides that, we'll specify a grid-gap value to give the images room to breath. We'll also specify automatic rows as single fractional units (1fr) using grid-auto-rows and new property that we have not talked about — grid-auto-flow and its value dense.

.gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    grid-auto-rows: 1fr;
    grid-gap: 1em;
    grid-auto-flow: dense;
}
Enter fullscreen mode Exit fullscreen mode

The grid-auto-* families of properties mean we are leaving some heavy lifting of the page layout to the browser. The browser will try its possible best but it's going to need our help at some point.

You'll realize in our code that we've not specified a grid-template-rows that will allow us to position the grid items in any row of our chosen, that's why we've used the grid-auto-rows that'll tell the browser to create implicit grid tracks of 1fr to accommodate the images if the need arises for it.

The grid-auto-flow property is used on grid items that have not been explicitly placed on the grid (like we just did) and it accepts properties like row, column and dense. We are interested in the dense value because I made use of it in our previous code snippet.

The specification states:

If specified, the auto-placement algorithm uses a “dense” packing algorithm, which attempts to fill in holes earlier in the grid if smaller items come up later. This may cause items to appear out-of-order, when doing so would fill in holes left by larger items.

In simpler terms, the dense property is telling the algorithm to attempt to fill gaps in the grid, even if it means changing the display order of some grid items.

For now, you might not notice the effect of this property but I will show you its effect later, for now, let's proceed with the layout.

When you turn on the browsers Developer Tools and inspect the grid items, you will realize that the images are not stretched to fill the grid area leaving behind some unused height.

Take a look at the image below, take note of the highlighted image and note the empty space under the figcaption.

Unused height in the grid item
Unused height in the Grid

We can fix this with Flexbox. All we have to do is apply display: flex to the <figure> elements via the gallery__figure selector.

.gallery__figure {
    display: flex;
}
Enter fullscreen mode Exit fullscreen mode

When you refresh your browser, you'll realize the images now occupy the entire grid area and there are no empty spaces under the figcaptions but we have another problem which is evident from the image.

Figcaption not aligned properly
Image captions not aligned properly

The figcaption elements are now beside the images, one quick fix for this is change the flex-direction to column.

Update your gallery__figure selector to match the snippet below.

.gallery__figure {
    display: flex;
    flex-direction: column;
}
Enter fullscreen mode Exit fullscreen mode

Refresh your browser and you will realize the unused height is back again but the figcaption now align perfectly.

Unused height in a grid area
Unused height in a grid

The fix is simple, we just have to make each specific image stretch to fill its grid item using the flex-grow property. This property is available as the first property of the flex shorthand property.

Add the following to your code and spaces should disappear.

.gallery__image {
    flex: 1;
}
Enter fullscreen mode Exit fullscreen mode

We are almost done. In our HTML we have some image with a class attribute of large-image. We have to make these images span 2 rows and 2 columns to make them really stand out.

.gallery .large-image {
    grid-row: span 2;
    grid-column: span 2;
}
Enter fullscreen mode Exit fullscreen mode

Save your code and refresh your browser. It's evident there are issues with the layout. They include:

  • The images are stretched
  • Each image width is small

Distorted image gallery
Image gallery

Let's fix the image width.

Prior to the code in the @supports(display: grid){} all figure elements via the gallery__figure selector have a maximum width of 300px, we need to change this. Update the gallery__figure selector to match the following.

.gallery__figure {
    display: flex;
    flex-direction: column;
    max-width: initial;
}
Enter fullscreen mode Exit fullscreen mode

Save and refresh your browser.

An almost finished image gallery
Image gallery

The next thing is to fix the stretched images.


The following snippet should be applied to the gallery__image selector outside the @supports(display: grid){} query.

All we have to do is make the image fit inside its parent container using the object-fit property with a value of cover.

Update the gallery__image selector outside the supports(display: grid){} to match the following.

.gallery__image {
    max-width: 100%;
    object-fit: cover;
}
Enter fullscreen mode Exit fullscreen mode

Save and refresh your browser, is everything all right?. If you are using Chrome (at the time of writing), I am pretty sure everything about the layout is not fine.

The image below is a view of the layout at a reduced zoom level of 50%. The entire layout is out of order. Did we do anything wrong? No. Do we have a bug in our code? I don't think so. Then what is the problem?

Image gallery with a reduced zoom level in Google Chrome
Image gallery in Chrome

It's the way Chrome calculates the height of images in a flex container and it's classified as a flexbug. There are two fixes that I know of that will make Chrome comply. They are:

  • Adding a width of 100 to the images
  • Or using flex shorthand with the value 1 0 auto, this is short for flex-grow: 1, flex-shrink: 0;, flex-basis: auto

Either will work, but let's use width: 100 to keep things simple. Update the gallery__image selector inside the @supports(display: grid){} to match the following:

.gallery__image {
    flex: 1;
    width: 100%;    /* Fix a bug in Chrome*/
    /* flex: 1 0 auto; Another fix for the bug in Chrome */
}
Enter fullscreen mode Exit fullscreen mode

Save and refresh your browser. The layout should work as expected in Chrome.

Here is the entire CSS code (with comments).

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    background-color: #807d6c;
    font-family: "Trebuchet Ms",Verdana, Georgia, sans-serif;
    padding: 0.7em;
}

.gallery__figure {
    margin: 0;
    display: inline-block;
    max-width: 300px;
}

.gallery__image {
    max-width: 100%;
    object-fit: cover;
}

.gallery__caption {
    padding: 0.3em 0.7em;
    background-color: rgba(0,0,0,0.5);
    color: #ffffff;
}

@supports(display: grid) { /* if browser supports grid */

    /**
     * The dense keuword used in the grid-auto-flow property
     * is telling the algorithm to attempt to fill gaps
     * in the grid, even if it means changing the display
     * order of some grid items
     *
     * The autofill used with the repeat() function will
     * allow the browser to place as many tracks onto
     * the grid as it can fit, with-out violating the restrictions
     * set by the specified size (the minmax() value).
     */
    .gallery {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
        grid-auto-rows: 1fr;
        grid-gap: 1em;
        grid-auto-flow: dense;
    }

    .gallery__figure {
        display: flex;
        flex-direction: column;
        max-width: initial;
    }

    .gallery__image {
        flex: 1;
        width: 100%; /* Fix a bug in Chrome*/
        /*flex: 1 0 auto; Another fix for the bug in Chrome */
    }

    .gallery .large-image {
        grid-row: span 2;
        grid-column: span 2;
    }

}
Enter fullscreen mode Exit fullscreen mode

One more thing!, Remember I said I would show you the effect of grid-auto-flow: dense? Now it's time.

Comment the grid-auto-flow: dense in the gallery selector declaration and refresh your browser. If you are on a large viewport, you'll notice no difference. Reduce your browser viewport you'll see lots of empty spaces inside the grid container because the grid placement algorithm is doing its best to arrange the images at that viewport which resulted in the empty spaces.

Absence of grid-auto-flow: dense
Abscence of grid-auto-flow: dense;

The grid-auto-flow: dense is telling the browser to fill the gaps in the grid even if it means changing the display order of some grid items. Now undo the comment and refresh your browser at the reduced viewport, everything should work as expected.

Effect of grid-auto-flow: dense
Effect of grid-auto-flow: dense;

And the Github repo:

GitHub logo ziizium / my-webdev-notes

Code snippets for series of articles on DEV about my experiments in web development

My WebDev Notes

This repositiory contains code snippets, and links for series of articles on DEV about my experiments in Web development.

List of articles






Have fun.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .