Create A Responsive Masonry Layout With Horizontal Flow

_martinwheeler_

Martin Wheeler

Posted on September 17, 2020

Create A Responsive Masonry Layout With Horizontal Flow

Introduction

I love online photo galleries. You can get lost in photography for hours, particularly when the photographer really has something special to show off. It’s with that in mind that, as a developer and designer, you feel that justice has to be done to those images.

Screenshot of the gallery

And this was my take on the infamous photo gallery project. I’m extremely happy with how it turned out and, although it may not seem much of an endeavour on the surface, it introduced me to a little something known as Masonry layout - and why there are a number of plugins and libraries developed to tackle just this. However, by the time I had decided what I wanted to achieve, I didn’t want to opt for a package to do this for me. I wanted to tackle this myself!

What is Masonry?

Masonry in web terms is the layout of unevenly sized tile, or brick, like elements in a way that there are only even gaps between them on both the x and y axis. It allows for a Pinterest-esque style look to your web page.

Representation of the masonry layout vertically

A quick search brings up multiple tutorials and suggestions from developers aiming to help you achieve this layout, in particular with a CSS only approach. I too really wanted to achieve this effect with only CSS, however there was always one downside to the solutions I found - the content flowed vertically. Given that this was a photographers website where content would be updated over time, it made sense to me that the images should flow from left to right, showing the newest uploads at the top of the page.

Representation of the masonry layout horizontally

As it turned out, I wasn’t able to get what I was looking for using CSS alone without having either fixed height rows or vertical flow, both using flex or grid.

Representation of a fixed height rows layout

Tools For The Job

As is common lately I chose React as my working platform. This was simply because I knew I would be using a lot of components when building the rest of this portfolio site and I like how React, utilising useState and useEffect, makes handling API requests easy. For the CMS I chose Cosmic (formerly CosmicJS), a headless CMS with a very easy to use REST/GraphQL API and user-friendly dashboard for the client. It also provides a seemingly limitless free-tier and Imgix compression abilities. Win win!

So, without further ado, let’s move on from the why and what, to the how. I’m not planning on going into depth on how the entire site was put together - it’s a fairly simple React site using react-router-dom. I should also say that this isn’t a tutorial on React, styled-components, or any of the other tools I’ve used. We’ll jump right in at the gallery part.

Getting It Done

I used the following file structure for the gallery part of the project:

File tree for the project

Let’s start by taking a look at GalleryPage.js. This is essentially the wrapper which all of the good stuff sits inside. From there, we can dig deeper.

Code: React imports

As always we make our start with our imports. As I mentioned at the beginning, the gallery makes use of the useState and useEffect hooks provided natively by React, so we bring those in along with React itself. We then import styled-components which, for anyone who is unfamiliar with it, is a fantastic library for utilising JS-in-CSS. Bear in mind, you won’t need styled-components to make this work - you can simply import normal CSS files or S/CSS modules into your project.

I generally like to structure my imports as above, with native or npm provided imports at the top, logic/hooks/etc imports just after that, and lastly any components. It’s not gospel of course, it’s just the way I like to keep myself organised.

I’ll come back to the rest of the imports soon. For now, let’s take a look at the rest of GalleryPage.

Code: React state and fetch function

There’s quite a lot going on there, so let’s break it down. First, we declare the component name which, in this instance, is GalleryPage. We then declare a number of variables which will be used to hold our site’s state.

isLoading and setIsLoading will, unsurprisingly, manage our loading sequence whilst we wait for fetch to retrieve our data, and imageData and setImageData will look after our data once it is retrieved. We’ll come back to the others shortly.

Using JavaScript’s fetch API we then make a request to the CosmicJS endpoint, for which you will need to get yourself an API key, and we receive back a JSON object. We can access the array we need under the object’s media key, so make sure that this is what you set as your state. Once we’ve set our state to our array using setImageData(data.media) we will now be able to use it to generate our images.

In the return statement for our component we need the following code:

Code: React component return statement and contents

The GalleryWrapper and GalleryWrapper elements have been created using styled-components, for now just think of them as any other React component. Inside here we map through our array and, ultimately, create our images. Now, the astute of you may have noticed something off in the code snippet above. We originally stored our array in the useState variable imageData, so why now are we trying to access something from sortedImageData?

This is where the important pieces of the puzzle come into play. Earlier we imported two additional modules - useWindowSize, which is a custom hook, and sortEveryNth, which is a JS function.

Code: React function imports

We’ll start by taking a look at the useWindowSize hook which is a custom hook with one important job to do - to listen out for any changes in our browser size and store that result in a state variable.

Code: useWindowSize react hook

The hook makes use of both the native useLayoutEffect and useState hooks, and essentially adds an event listener to the window which fires each time a resize event occurs. This is the stored, and returned, as the constant size. To have a look into useLayoutEffect I recommend the docs.

Now we’ll take a look at the sortEveryNth function and see how this all fits together.

Code: sortEveryNth function

This function takes in two parameters, an array (the one that we want sorted) and a single number. This single number will reflect two things - the amount of columns we want to have in our gallery layout and, subsequently, the iteration count for the sorting algorithm.

Code: useWindowSize import

Code: useEffect hook for handling state changes relating to the window event listener

The width we are using within this useEffect hook is the value that is being returned from the useWindowSize we just created, and from this value it is calculating whether to change state at some arbitrary break points - in this case 1366px, 1024px, and 800px.

Let’s say for example our browser width increases to, or is initially set at, 1400px. This fits into the first condition of the if statement, being greater than 1366px. setSortedImageData now calls the sortEveryNth function and passes in the imageData array to the first parameter, and the value of 4 as the second. The function now begins its work sorting the objects in the array by their index and returning a new array.

A representation of how the function works on each element in the array

As this image hopefully explains well, the function skips through each item by n, which is in this case 4, and pushes the object into the new array. Once this is complete, the new array, imaginatively named newArr, is returned back to setSortedImageData and consequently stored into sortedImageData. And, after all of that, this is where we are mapping our data from, creating our GalleryImage components and appending them onto GalleryWrapper. The useEffect hook has both width and imageData in its dependency array, and these are responsible for ensuring everything is re-rendered once any changes occur to the browser size.

That’s essentially all of the heavy lifting out of the way. The last part to be put in place, to make sure everything works, is the CSS. I found that the use of column-count gave both the best and most predictable results.

Code: A styled-component showing the required CSS

It’s important to use media queries at the same breakpoints as you set in the useEffect hook as these will work in unison to both lay out the page and calculate the sorting correctly. As you can see I actually started this desktop first - not intentionally, it was just how it happened. And, as I mentioned before, any CSS will work here so don’t get hung up on how this looks outside of the CSS.

And that’s it! I hope I was able to share something interesting with you here, and I’d really appreciate any feedback on either the content or the writing. This is my first ever post, and I’d like to do this more often and making it worthwhile would be a massive bonus.

You can check out anything related to me or this project on my website

💖 💪 🙅 🚩
_martinwheeler_
Martin Wheeler

Posted on September 17, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related