DEV Community

Joe Dietrich
Joe Dietrich

Posted on

Build a fixed-aspect-ratio photo gallery with hover effects (using only HTML & CSS!)

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.

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:

<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>
Enter fullscreen mode Exit fullscreen mode

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

.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;
}
Enter fullscreen mode Exit fullscreen mode

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:

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:

  <div class="tile-overlay tile-overlay-bottom">
    <p class="tile-overlay-text">Hello, World!</p>
  </div>
Enter fullscreen mode Exit fullscreen mode

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.

.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;
}
Enter fullscreen mode Exit fullscreen mode

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:

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:

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

.grid-tile:hover .tile-overlay {
  opacity: 100%;
}
Enter fullscreen mode Exit fullscreen mode

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 hovers over a grid-tile, I want the opacity of the tile-overlays 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:

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:

  <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>
Enter fullscreen mode Exit fullscreen mode

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.

.tile-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
Enter fullscreen mode Exit fullscreen mode

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:

.grid-tile {
  background-color: rgba(0, 0, 0, 0.1);
  aspect-ratio: 1;
  position: relative;
  overflow: hidden;
}
Enter fullscreen mode Exit fullscreen mode

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

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:

.tile-image {
  position: relative;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: 0.33s;
}
Enter fullscreen mode Exit fullscreen mode

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.

.grid-tile:hover .tile-image {
  top: -5px;
  left: -5px;
  width: calc(100% + 10px);
  height: calc(100% + 10px);
}
Enter fullscreen mode Exit fullscreen mode

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:

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

Discussion (0)