Create A Responsive Masonry Layout With Horizontal Flow
Martin Wheeler
Posted on September 17, 2020
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.
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.
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.
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.
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:
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.
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
.
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:
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.
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.
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.
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.
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.
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.
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
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
March 23, 2021
October 18, 2019