Build a fixed-aspect-ratio photo gallery with hover effects (using only HTML & CSS!)
Tags: html, css, webdev
Published: 2021-10-22 02:56:09

In this article, I'll explain how to create a gallery of photos with text overlays using modern CSS and HTML, as seen in the Code Sandbox below. 

{% codesandbox zen-keldysh-096lt %}

## Background
As I was building out a test photo gallery, app I realized that I wanted my main page to be a grid of square images. The pictures were supposed to be the star of the show, but I wanted the viewer to be able to see a description of each image when they hovered over it. To spice it up a little, I decided to add a "zoom in" effect on hover.

My goal was to use modern CSS and HTML only, if I could. I decided to start with CSS Grid as a base.

## Step One - The Grid
The first step was building out the grid itself. To start with, I created a container div and a few tiles:

```html
<div class="grid-container">
  <div class="grid-tile"></div>
  <div class="grid-tile"></div>
  <div class="grid-tile"></div>
  <div class="grid-tile"></div>
  <div class="grid-tile"></div>
  <div class="grid-tile"></div>
  <div class="grid-tile"></div>
  <div class="grid-tile"></div>
</div>
``` 

The `grid-container` will hold all our `grid-tiles`. The `grid-tiles` will then hold the images and the overlays.

```css
.grid-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 20px;
}

.grid-tile {
  background-color: rgba(0, 0, 0, 0.1);
  aspect-ratio: 1;
}
```

Here, we set the `grid-container` to display as a grid with a 20px gap between its rows and columns. We also define the width of the columns of the grid in `grid-template-columns`. With `repeat(auto-fit, minmax(150px, 1fr))`, we declare that we want to create columns that are at least 150px and will otherwise evenly grow to fill the available width of the container (`minmax(150px, 1fr)`). The `auto-fit` keyword will only create as many columns as we need - if we only have two child elements, only two columns will be created.

> For a great auto-fit / auto-fill breakdown, check out (Auto-Sizing Columns in CSS Grid)[https://css-tricks.com/auto-sizing-columns-css-grid-auto-fill-vs-auto-fit/]

The critical part of the `grid-tile` style is `aspect-ratio: 1`. This tells the browser that we want square tiles. `aspect-ratio` accepts whole numbers or fractions - without units - in the format `<width> / <height>`.

> **Note** `aspect-ratio` is supported in the most recent version of all major browsers as of this writing, but support does not include Internet Explorer or Safari before version 15. You can replicate some of this without `aspect-ratio` by setting `grid-template-columns: repeat(auto-fit, 150px);` and `grid-auto-rows: 150px;` in the `grid-container` class. You lose flexibility, but maintain the fixed aspect ratio.

With these rules in place we should see something like this:

{% codesandbox zen-keldysh-096lt initialpath=/step_one/index.html %}


## Step Two - The Overlay
Now that we have the grid itself, we can add a text overlay.

We'll add the following code inside each of our `grid-tile` divs:

```html
  <div class="tile-overlay tile-overlay-bottom">
    <p class="tile-overlay-text">Hello, World!</p>
  </div>
```

The a `tile-overlay` div will contain an elements that we want to appear on hover over the tile. The `tile-overlay-bottom` styles will ensure the element appears on the bottom of the tile. We'll style `tile-overlay-text` for readability on a darker background.

We need the tile to have `position: relative` so we can place its children absolutely, otherwise, the children will be positioned relative to the window itself, which isn't what we want.

```css
.grid-tile {
  background-color: rgba(0, 0, 0, 0.1);
  aspect-ratio: 1;
  position: relative;
}

.tile-overlay {
  position: absolute;
  background-color: rgba(0, 0, 0, 0.75);
}

.tile-overlay-bottom {
  bottom: 0;
  width: 100%;
}

.tile-overlay-text {
  margin: 0;
  color: white;
  padding: 10px;
}
```

> As a challenge to yourself, try to implement an overlay element that will appear in the top-right of each tile.

At the end of Step Two, you'll have something like this:

{% codesandbox zen-keldysh-096lt initialpath=/step_two/index.html %}


## Step Three - Overlay Hover Effect
So far, we have a grid of tiles and we have an overlay. That's great, but the overlay is there all the time and I promised you hover effects.

To get us there, we'll need to add a couple lines to our CSS:

```css
.tile-overlay {
  position: absolute;
  background-color: rgba(0, 0, 0, 0.75);
  opacity: 0%;
}

.grid-tile:hover .tile-overlay {
  opacity: 100%;
}
```

With these few lines of code, we're giving everything with the `tile-overlay` class an opacity of 0%, which means the element will still take up its usual space, but will not be visible. 

The next rule says, "Every time a viewer `hover`s over a `grid-tile`, I want the `opacity` of the `tile-overlay`s to go to 100% (be completely visible)."

And with that, we have one of our hover effects!

> For a smoother `transition` between opacity 0% and opacity 100%, you can insert a `transition` property into the `.tile-overlay` rule: 
>
> `transition: opacity 0.33s;`
>
> This will extend the length of the transition from 0% to 100% opacity from instant to a third of a second.

The end of Step Three should look like this:

{% codesandbox zen-keldysh-096lt initialpath=/step_three/index.html %}


## Step Four - Adding Images
So far, we've built the cards and an overlay, but our photo gallery isn't any good if there aren't any photos! Start off by adding `img` tags to your `grid-tile` divs. When you're done, the `grid-tile` html should look something like this:

```html
  <div class="grid-tile">
    <img
      class="tile-image"
      src="https://source.unsplash.com/random/200x250"
    />
    <div class="tile-overlay tile-overlay-bottom">
      <p class="tile-overlay-text">Hello, World!</p>
    </div>
  </div>
```

If you check out your page now, you'll see that the images are overflowing the tiles, so we're going to need to turn to CSS for help.

```css
.tile-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
```

This new CSS rule will constrain our images to the height and width of their parent - in this case, the `grid-tile`. A value of `cover` for the `object-fit` property means that the image will maintain its aspect ratio, but anything outside of the parent element will be clipped. This keeps our images from being distorted, but means we lose some of the image content.

> One last thing to note for this phase. You may have noticed that our tiles are no longer perfect squares, depending on your browser. There may be a small strip of gray background below the image in each tile. I believe this has something to do with the image distorting the grid, but I haven't been able to figure it out yet. If you know why this happens, please leave a comment!
>
> We can fix that strip pretty easily by adding `overflow: hidden` to the `grid-tile` rule. This will also be important for the next step, so let's go ahead and do it now:

```css
.grid-tile {
  background-color: rgba(0, 0, 0, 0.1);
  aspect-ratio: 1;
  position: relative;
  overflow: hidden;
}
```

By the end of Step Four, you'll have something like this:

{% codesandbox zen-keldysh-096lt initialpath=/step_four/index.html %}


## Step Five - The Image Zoom Hover Effect
What we have is great so far, but let's jazz it up a little. We want it to look like the image is coming slightly closer to you when you hover over it.

First, we'll add some styles to the image to provide a baseline for what we'll do on hover:

```css
.tile-image {
  position: relative;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: 0.33s;
}
```

These styles give us a baseline for our transition on hover. They tell the browser that we're consciously placing our image exactly where it normally goes in the flow of the document. Note also the height and width. Those will change when we hover over the `grid-tile`.

```css
.grid-tile:hover .tile-image {
  top: -5px;
  left: -5px;
  width: calc(100% + 10px);
  height: calc(100% + 10px);
}
```

With this, when a visitor hovers over the `grid-tile`, the image will grow 10px in width and height (the `calc` statement makes sure of that!), at the same time, we're shifting the image 5 pixels up and to the left. This makes sure that the effect we're going for is centered in our tile. Without the position change, it would look like the image is just growing from the upper left.

You might ask, "But if the image is growing, why isn't the tile growing too, and changing the layout!?" This is where the `overflow: hidden;` rule from the last step comes in. Since the overflow is set to hidden, any change in the image size will be clipped!

This leads us back to where we started out:

{% codesandbox zen-keldysh-096lt initialpath=/step_five/ %}

If you liked this, or have questions, let me know!