On the web it is common to see this kind of layout when you’re browsing a plethora of content with no discernible structure:
Your eye is naturally drawn to shapes you recognize. Our visual cortex can only process so much information at one time and our pea-sized attention spans can’t keep up. Whatever is not recognized is filtered out, and while it appears in your visual field it does not imprint. It’s called a masonry layout and I’m here to tell you it sucks.
In the days of the early mechanical printing columns were used because type blocks had to be manually arranged and later were formed on demand. This was no easy task. As we transitioned to modern printers, columns persisted because it allows more various articles above the fold and works well with advertising.
So why do we still use these layouts on the web in the year 2017? For the very same reasons: advertising and loosely related content. If you are deciding whether or not you should use a masonry layout, the answer is almost always no. There is one exception: when you can’t sort your content by order of appeal.
Isotope, Packery, Masonry.JS, Bricks.JS — these are all wonderful implementations of what a masonry layout looks like. And they power many websites because of their ease of use and the features they offer. The problem however, is they aren’t built for speed and deceive the developers using them.
In 2016 I was tasked with developing an entirely new React web application for The Hunt. This is how the website looked before:
There is no doubt that this grid display is dated, but it’s also really easy to navigate because it’s linear. It flows like a book, line by line.
If I could go back and make a better case for that layout, I would.
We started with Isotope JS. I’ve used it before in previous projects and it does alright for lighter websites and static blogs. In development I never noticed any performance issues because I running an i7 Intel 3.4GHz processor and 16GB of RAM.
When we launched our users were reporting crashes and laggy scrolling. I dug into the library and found the APIs it was hitting were much slower and out of date, causing unnecessary browser repaints. The other problem was Infinite Scroll, another bad user interface pattern we can’t seem to get rid. But that’s a whole other subject.
In this case, the infinite scroll and masonry layout were loading hundreds of DOM elements per page and repainting far more than necessary. What good is a masonry layout if your users can’t get past page three on their netbooks and tablets?
I combed through many of the masonry layout libraries out there. Against the recommendations of just about everyone I talked to, I knew I had to do the one thing I didn’t want to do.
Sometimes you have to start from scratch. What we need from a masonry layout is not the same others need. Many masonry layouts have wonderful features like variable element sizing and drag-and-drop.
I set out to figure out why some of these libraries were slow. What I found is that many of the features that make them flexible also hurt. For example variable sizing is a huge performance drain: measuring a hundred element size models is expensive. And in some instances a library would use methods that would cause an unnecessary repaint to get the width and height. Another issue is that all of these elements add up and as the page grows, the calculations incur exponentially.
Then I looked at Pinterest — the pinnacle of masonry. I wondered how the engineers managed to keep it fast and usable.
The process involved a lot of math and patience — calculating where to place items and how to place them efficiently. For example when an item spans multiple columns it will create a gap if the items above do not align. This was the most challenging aspect because we didn’t want a fast masonry layout with empty whitespace everywhere. All of this had to fit inside a React Component and go with the grain of lifecycle events. So when infinite scroll loads a new page only new items should layout.
And what a beautiful React Component it turned out to be. I decided to write this post to document both the frustration and immense pride I felt for this effort. When I reflect on the process and the result — I see some of my best work. Trying and trying, iteration after iteration; the “are we there yet” moments. The voice at the back of my head saying “don’t write a layout engine.”
The Result: a fast Masonry layout built in React
It turned out to be incredibly performant. On a Chromebook there were no hiccups and rendering was faster than it took for the server to return the next page. I immediately made the component available via Github (check it out if you want to see what’s under the hood). It’s not available as a package because it’s not “one size fits all” and the last thing the web needs is another masonry library.
You can see this engine running live @ thehunt.com
If you are thinking of rolling your own layout engine: proceed with caution. There are a plethora of resources available for Flexbox, CSS4 Grids, and performant interfaces on the web. Seek out better user experiences that flow naturally. If you are looking to do it yourself then I would suggest doing your research and eliminating over-engineered features.
I hope you enjoyed reading this: if you feel the pain of masonry layouts or you’ve been there before feel free to share your experience.