Masonry style layout with CSS Grid

A flexible, responsive Masonry-style layout using CSS Grid and a small amount of JavaScript

I’ve been working on a way of using CSS Grid and a small amount of JavaScript to make Masonry style layouts. I reproduce all the functionality of Masonry whilst remaining flexible and responsive thanks to CSS Grid.

Before we dive in to the method, here is a CodePen with the complete solution. (Warning : The browser compatibility of CSS Grid has rapidly grown but still isn’t supported by all mobile browsers).

The completed Masonry solution.

What is Masonry?

Masonry is a JavaScript library that lays out content of different sizes without leaving gaps. It’s a very popular layout and you will have seen it all over the internet.

Here are some examples I have taken directly from the Masonry site:

Masonry layout on http://www.kristianhammerstad.com/, https://staff.tumblr.com/archive and http://iam.beyonce.com/.

The key functionalities of Masonry that we will be replicating are:

  • Content blocks have a consistent width but variable height.
  • They are arranged in columns with blocks placed directly below the block above.
  • They are ordered from left to right,then top to bottom. The first blocks go at the top of each column with later blocks placed in whichever column is the shortest. (I.e. the block is placed as high up the page as possible regardless of column)
  • The layout can dynamically cope with extra blocks. This makes it ideal for handling layouts where more content is loaded onto the bottom of the page. (Either with a “load more” button or automatically when the user has scrolled to the bottom.)
A masonry style layout with each additional block being added to the shortest column.

What is CSS Grid?

CSS Grid greatly simplifies creating grid based layouts for the web. It allows content to be placed explicitly in the grid at a defined location. It is also excellent at placing content automatically in the next available space. This “auto-flow” functionality is what we will be using for our Masonry-like solution

No-one knows more about CSS Grid than Rachel Andrew. Her site https://gridbyexample.com is the absolute “must read” for anyone looking to get started. If you haven’t yet used CSS Grid look at Rachel’s site to get an understanding of what CSS Grid is and what it is capable of.

You’ll find that you can make both practical and beautiful layouts using CSS Grid and there are plenty of examples:

Why try and replicate Masonry using CSS Grid?

CSS Grid has made arranging content much easier. It can be used to easily make content grids responsive. The built-in “flow” property optimises the grid layout and minimises white space between elements.

However it doesn’t provide a way of automatically producing a Masonry style layout. The main reason being that CSS Grid creates two dimensional grids, i.e. grids with strict rows and columns. Rachel Andrew has written a great post on the subject: CSS Grid. One layout method not the only layout method.

A 2 dimensional grid with each block being arranged strictly by column AND row.

As Rachel mentions, a Masonry like effect can be created by having blocks that span rows but CSS Grid can’t apply this automatically.

Rachel concludes that CSS Grid probably isn’t the right solution if you are looking for a Masonry like layout. However I would argue that because CSS Grid handles most of the grid requirements so elegantly (responsive layout, variable width AND height blocks, packing) it is worth using it as the basis of a Masonry style layout. By adding just a small amount of JavaScript we can achieve the automated row spanning required.

Below is a screenshot of the approach we will use. Each block spans a number of rows. The challenge will be to dynamically set this number based on the content of the block.

Each block fits in the rigid 2D grid but by spanning multiple rows blocks can have variable heights.

The proposed solution won’t have the full flexibility of Masonry. The height of each block can’t be any arbitrary height but will need to fit to an exact number of grid rows. The smaller you set the height of your rows and row-gaps, the more flexibility you will have.

You will have to decide whether this is a problem for your design or not. With all that said, lets get on and start making the layout.

Some content

I’ll start off with three types of example content:

  • Blogs: A simple block of text
  • Photos: A photo with a short caption
  • Projects: A block of text with a supporting image

We’ll create a <div> for each item and put them all within a single holder <div>. I’ve applied some additional elements within each content type for formatting and added some CSS. We end up with 12 content items with some basic styling but none that affects their layout. As such they simply appear one after each other:

The Grid

So lets start off by putting the content in a grid. To do this we simply need to set the CSSdisplay property of the single containing <div> to grid. We add the class grid to the <div> and define its properties as follows:

.grid {
display: grid;
grid-gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(250px,1fr));
}

We have also set two other properties:

grid-gap is straightforward and is simply the spacing between grid items

grid-template-columns is more complicated and it’s the way we define the columns of the grid. It’s simple enough to set up a defined number of fixed width columns but we are going to build our responsive-ness in using this parameter.

The value we use repeat(auto-fill, minmax(250px,1fr)) specifies the following:

  • repeat create multiple similar columns
  • auto-fill create as many columns will fit in the available grid width
  • minmax(250px,1fr) the columns should have minimum width 250px and a flexible maximum width which is the same for each column

This means the grid is filled with as many 250 pixel width columns as possible. As the grid gets larger the columns all increase equally in width until there is enough space for an additional 250 pixel column.

The number of columns increases with the size of the window.

This Codepen shows the code so far and allows you to play with the column settings.

Needless to say there is no better place to learn about how to use these properties than Rachel Andrew’s tutorial videos.

So already we have a simple grid layout.

  • The items are arranged into columns keeping their given order.
  • The row heights automatically adjust to the largest item on that row.
  • The grid automatically moves items into new columns as space allows.

Spanning multiple rows

At the moment the rows are automatically sizing to allow the largest items in each row to be fully displayed. We will instead restrict the row sizes to a predetermined height:

.grid {
display: grid;
grid-gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(250px,1fr));
grid-auto-rows: 200px;
}

With each item still restricted to just one row this means that all items are now too tall for the row and some of the content in each item is not displayed.

Content is cropped in each item at the new defined row height.

If each of our content types was a consistent height we could set each of our content types to span a different number of rows:

.photo {
grid-row-end: span 2;
}
.project {
grid-row-end: span 3;
}
.blog {
grid-row-end: span 1;
}
Different content types spanning different numbers of rows.

The code for this approach is shown in this Codepen.

However, as the screenshot shows, this won’t work for our content as even content of the same type can be of different sizes. Therefore we will remove the code we have just added setting each content type to span a set number of rows.

Instead this is where we will introduce JavaScript. We will go through each individual item and set the number of rows that it spans so that it is tall enough for all the content to be displayed.

Dynamically setting the row span

Firstly, within each item we place another <div> that holds all the content and give this the class content.

<div class="item blog">
<div class="content">
// CONTENT GOES HERE
</div>
</div>

This means we will be able to compare the height of this “content” div with the height of the grid item containing it. The aim will be to make the grid item just large enough to display all the content without being too large to create excess white space.

Next we write a JavaScript function that resizes an item so that it is just large enough to display all the content within it.

The function looks at the CSS already applied to the grid. From this it finds the height of the rows (grid-auto-rows) and the vertical gap between rows (grid-row-gap).

Now we know the height of each row and gap we can calculate the number of rows required to accommodate the height of the item content.

This calculated value is then applied to the item’s grid-row-end value.

function resizeGridItem(item){
grid = document.getElementsByClassName("grid")[0];
rowHeight = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-auto-rows'));
rowGap = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-row-gap'));
rowSpan = Math.ceil((item.querySelector('.content').getBoundingClientRect().height+rowGap)/(rowHeight+rowGap));
item.style.gridRowEnd = "span "+rowSpan;
}

This function resizes items individually. We also need a function that allows all items to be resized at once. This function finds all the items in the grid and loops through calling the individual resize function on each one.

function resizeAllGridItems(){
allItems = document.getElementsByClassName("item");
for(x=0;x<allItems.length;x++){
resizeGridItem(allItems[x]);
}
}

This means we can call the function that resizes all items once the page has loaded.

window.onload = resizeAllGridItems();

The width of the columns also changes when the browser is resized so we will also call this function on window resize.

window.addEventListener("resize", resizeAllGridItems);

Finally, although we call the “resize all items” function when the page is loaded it doesn’t necessarily mean all the images on the page have been loaded. As images load they increase the size of the content they are in. These items may therefore need to span more rows than at first calculated.

To handle this I will use the imagesLoaded.js library. This is able to call a function when the images within an element have loaded.

After including the library to our project we attach the imagesLoaded handler to all grid items and call a new function resizeInstance.

allItems = document.getElementsByClassName("item");
for(x=0;x<allItems.length;x++){
imagesLoaded( allItems[x], resizeInstance);
}

This function passes the individual grid item on to our already existing resizeGridItem function.

This means that when all the images have finished loading within a grid item it is resized to ensure that the content is still fully displayed.

function resizeInstance(instance){
item = instance.elements[0];
resizeGridItem(item);
}

That is all the JavaScript that is needed. Now when the page loads the height of each item is calculated and the number of grid rows it spans is set dynamically. Then when any images load or the page is resized the height of the items are again calculated to ensure they still fit. This CodePen shows it working:

The number of rows each item spans is set dynamically.

Optimising Row Height

Some items have a large amount of whitespace at the bottom. This is because the items span the number of rows required to be larger than their content height. If you look closely at the screenshot below the rows and spacing have been drawn over the grid. You can see that Item 4 is just taller than 2 rows so is set to span 3 rows. This leaves quite a large gap unfilled.

Item 4 has a large amount of whitespace at the bottom.

The solution is to set the height of the rows to a smaller value:

.grid {
display: grid;
grid-gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(250px,1fr));
grid-auto-rows: 40px;
}

Each item will span more rows but there is less whitespace at the bottom of items.

A smaller value of row height means there will be less whitespace at the bottom of each item.

The End

And that’s it. I hope I have shown you a simple way to make a standard Masonry layout using CSS Grid and just a smidgeon of JavaScript.

I hope you find this useful, if so let me know. And of course if you see any way to improve this approach or any issues you feel should be resolved please leave a comment.

Here is a last link to the full project on CodePen.