DEV Community

Cover image for Masonry with CSS grid
Jenning Ho
Jenning Ho

Posted on • Updated on

Masonry with CSS grid

A month ago codepen published a challenge to create a photo gallery. Oh, I know photo gallery, I've made a gallery or two, I thought. So I decided to give it a go.

What you see in the cover photo is my submission for the challenge. Thanks thecatapi.com for the amusing cat photos.

Masonry layout can be unimaginative when it comes to photo gallery (what's masonry layout?), but I had an idea to build it with CSS grid. CSS grid is powerful and you should pick it up if you haven't already. It allows you to effectively layout a webpage, but using it to build masonry layout can still be considered fairly novel. What's the secret?

There are a few key points that I'll cover:

  • square grid layout
  • varying item sizes and packing them together
  • content that doesn't disrupt the container size

1. Square grids

We need square grids to allow the photos to be laid out in a way that makes sense. The square grids will also need to be responsive where it will take up available space. To do that I applied a technique to size an element to specific aspect ratio. The trick is sizing an element vertically with padding as opposed to height. Percentage paddingvalue is calculated based on the element's width. i.e. equal % of width and padding-bottom will give you a square.

We don't have to apply this to every grid item (the children of a grid container). This is only applied to 1 element, the :before pseudo element of the grid container, like so:

.container:before {
  content: '';
  height: 0;
  width: 100%;
  padding-bottom: 100%;
}
Enter fullscreen mode Exit fullscreen mode

This squared :before pseudo element will serve as the blueprint for all other grid item. However, this itself is not enough to generate a square grid layout yet. We will need to couple it with CSS grid's property grid-auto-rows: 1fr and grid-template-columns: repeat(var(--number-of-columns), 1fr) to form the layout.

  • grid-template-columns: repeat(var(--number-of-columns), 1fr) to setup the number of columns for your layout
  • grid-auto-rows: 1fr will force each row to be of equal size.
.container {
  display: grid;
  grid-auto-rows: 1fr;
  grid-template-columns: repeat(6, 1fr);
}
Enter fullscreen mode Exit fullscreen mode

With this the grid items will be sized into squares, and will be laid out in 6 columns. This is still not the end of square grids. You'll notice that there is an empty gap at the first grid. That is the :before pseudo element taking up space. To seal this up, move both the pseudo element and the first grid item to the first block.

.container:before {
  content: '';
  height: 0;
  width: 100%;
  padding-bottom: 100%;
  grid-column: 1 / 1;
  grid-row: 1 / 1;
}

.grid-item:first-child {
  grid-column: 1 / 1;
  grid-row: 1 / 1;
}
Enter fullscreen mode Exit fullscreen mode

Now your square grid layout is ready.

2. Varying item sizes and packing them together

To create the varying sizes of a masonry block, we size the grid items using grid-column: span and grid-row: span. Then we use :nth-child to simulate the randomness of a masonry layout. In my pen, I only size items up to 2 blocks, so the code looks like:

/**
  every 3rd item takes up 2 blocks horizontally (2 x 1)
**/
.grid-item:nth-child(3n) {
  grid-row: span 2;
}

/**
  every 4th and 7th item takes up only 1 block (1 x 1),
  to overwrite the previous 3n rule
**/
.grid-item:nth-child(4n),
.grid-item:nth-child(7n) {
  grid-column: span 1;
  grid-row: span 1;
}

/**
  every 5th item takes up 2 blocks vertically (1 x 2),
  also every 15th block will be a big one (2 x 2)
**/
.grid-item:nth-child(5n) {
  grid-column: span 2;
}
Enter fullscreen mode Exit fullscreen mode

There are some numbers you can tweak to get the gallery to cut off perfectly, (i.e. column count vs item count), and you can tweak the numbers to get a layout that you like. There are no hard rules here. This happen to be the layout that I'm happy with.

The grid items are now seemingly sized randomly like a masonry, but there are still empty blocks scattered in between, due to the grid item not able to fit in after sizing up.

To fix that we apply grid-auto-flow: dense to pack them up. This will force the single block item squeeze into the empty blocks.

Now you'll see a respectable but empty gallery.

2. Content that doesn't disrupt the container size

To maintain the perfectly squared grid item, the content (the photos) cannot be pulling or pushing the container (the grid item itself), as that'll cause the square to collapse at the next row. We can do this by inline-styling the image url with background-image property, but that would be terrible for accessibility. So we'll do it the hard way (just kidding), by using the <img> tag and styling it with CSS.

First we want the image to not affect the container. To do that we apply position: absolute to the image and that will take it out of the document flow.

.grid-item {
  position: relative;
}

.photo {
  position: absolute;
}
Enter fullscreen mode Exit fullscreen mode

Next we want to size the image so that it'll fill up the container of varying sizes. To do this we apply

.photo {
  position: absolute;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
Enter fullscreen mode Exit fullscreen mode

Now you get a beautifully filled up photo gallery!

Conclusion

Originally I only wanted to highlight the square grid layout technique, but figured a tutorial to build a full feldged photo gallery with css might be more interesting. There are other designs where I've applied the square grid technique too, where you need responsive squares or circles, i.e. the Olympic logo. Be creative! I hope you'll add this to your arsenal of CSS techniques!

Some related helpful resources:

Top comments (1)

Collapse
 
alohci profile image
Nicholas Stimpson

You might be interested in this proposal to extend Grid layouts to support masonry more simply: github.com/w3c/csswg-drafts/issues...