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!
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.
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
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.
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
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
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.
setIsLoading will, unsurprisingly, manage our loading sequence whilst we wait for fetch to retrieve our data, and
setImageData will look after our data once it is retrieved. We’ll come back to the others shortly.
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:
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
imageData, so why now are we trying to access something from
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
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.
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
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