Let’s take a look at a super lightweight way to create a horizontal masonry effect for a set of arbitrarily-sized photos. Throw any set of photos at it, and they will line up edge-to-edge with no gaps anywhere.
The solution is not only lightweight but also quite simple. We’ll be using an unordered list of images and just 17 lines of CSS, with the heavy lifters being flexbox
and object-fit
.
Why?
I have two hobbies: documenting my life with photos, and figuring out interesting ways to combine CSS properties, both old and new.
A couple of weeks ago, I attended XOXO and shot a ton of photos which I narrowed down to a nice little set of 39. Wanting to own my content, I’ve spent the past couple of years thinking about putting together a simple photo blog, but was never able to nail the layout I had in mind: a simple masonry layout where photos fill out rows while respecting their aspect ratio (think Photos.app on iOS, Google Photos, Flickr…).
I did some research to see if there were any lightweight, non-JavaScript options, but couldn’t find anything suiting my needs. Facing some delayed flights, I started playing around with some code, limiting myself to keep things as simple as possible (because that’s my definition of fun).
Basic Markup
Since I’m basically creating a list of images, I opted for an unordered list:
<ul>
<li>
<img>
</li>
<!-- ... -->
<li>
<img>
</li>
</ul>
All hail flexbox
Then came a string of lightbulb moments:
- Flexbox is great for filling up rows by determining cell width based on cell content.
- This meant the images (landscape or portrait) all needed to have the same height.
- I could use
object-fit: cover;
to make sure the images filled the cells.
In theory, this sounded like a solid plan, and it got me a result I was about 90% happy with.
ul {
display: flex;
flex-wrap: wrap;
}
li {
height: 40vh;
flex-grow: 1;
}
img {
max-height: 100%;
min-width: 100%;
object-fit: cover;
vertical-align: bottom;
}
Note: 40vh
seemed like the best initial approach for desktop browsers, showing two full rows of photos at a reasonable size, and hinting at more below. This also allowed more photos per line, which dramatically improves the aspect ratios.
Last row stretchiness
The only issue I ran into is that flexbox really wants to fill all the lines, and it did some silly things to the aspect ratios of the photos on the last row. This is probably my least favorite bit of this layout, but I had to add an empty <li>
element at the end of the list.
<ul>
<li>
<img>
</li>
<!-- ... -->
<li>
<img>
</li>
<li></li>
</ul>
Combined with this bit of CSS:
li:last-child {
flex-grow: 10;
}
Note: There’s no science in using “10” here. In all my testing, this delivered the best results.
Demo
See the Pen
Adaptive Photo Layout by Tim Van Damme (@maxvoltar)
on CodePen.
Viewport optimization
There are some considerations to keep in mind when working in different viewport orientations.
Portrait
If your viewport is taller than it is wide, this approach limits the amount of photos per line thus messing up their aspect ratios. To solve this, you can make the photo rows less tall with this simple media query:
@media (max-aspect-ratio: 1/1) {
li {
height: 30vh;
}
}
Short Screens
To help with small devices in landscape, increasing the height of photos helps to see them as large as possible:
@media (max-height: 480px) {
li {
height: 80vh;
}
}
Smaller Screens + Portrait
Most phones aren’t wide enough to allow flexbox to properly do its job without miniaturizing the photos, so here I opted to not try to fit multiple photos per line. Still, it’s worth setting a maximum height here so you’ll at least have a hint at the next photo in the list.
@media (max-aspect-ratio: 1/1) and (max-width: 480px) {
ul {
flex-direction: row;
}
li {
height: auto;
width: 100%;
}
img {
width: 100%;
max-height: 75vh;
min-width: 0;
}
}
There we have it!
This approach doesn’t fully respect the aspect ratios of photos (but it’s close) and occasionally leads to some weird results, but I absolutely love the simplicity and flexibility of it all. Want to have your gallery scroll horizontally instead of vertically? A couple of tweaks will allow you to do this. Are there 1,000 photos in the gallery or just one? It’ll all look good. Unclear about aspect ratios? Flexbox is your best friend. Take another look at the demo if you haven’t yet, and let me know what you think!
Bonus
Depending on the size of these photos, a page like this can grow to multiple megabytes real quick. On the blog I’m working on, I’ve added loading="lazy"
to help with this. With that attribute in place on the images, it only loads photos once you approach them while scrolling. It’s supported just in Chrome for now, but you could roll your own more supported technique.
Nice work! It was very easy to fork a version with gutters for a different feel:
Very nice, I was considering trying this myself before seeing this comment.
Very cool. You can get rid of the last
li
you’re not fond of by movingflex-grow:10
to aul:after
pseudo element:See the Pen
Adaptive Photo Layout by Will Anderson (@andwilr)
on CodePen.
No need for that empty
<li></li>
at the end. You can append a pseudo element to the<ul>
and it will act like the last flex item:ul::after {
content:"";
flex-grow: 10;
}
Great I was just trying to achieve this layout! I found that adding a flex-basis to the last item (which I think you can replace by an
::after
) can help the last images to fill up the last line.Hi there,
This is a quite interesting way of creating a lean photo-grid.
One thing though: Instead of adding an empty li at the end you could use the after element on the grid with a zero height and flex-grow: 10.
Using height: 0 has no effect, if the element is pushed to a row on it’s own while keeping up the effect of pushing the elements to the left.
This should do the job without adding an extra empty element.
Y’all are amazing! Using
::after
totally slipped my mind.Thank you for sharing!
I was playing with your code (and some pics of mine) and I could notice that there are sometimes when some pics doesn’t cover the height properly so you can see the background below the pic. Any idea how could i fix this behavior?
I haven’t come across this issue! Which browser are you using?
Is there a way to make this work in Bootstrap 4? They layout there isn’t really working, I guess because of the default styles for <ul> and <li>.
I’ve never used Bootstrap (always roll my own system)
Hi – very cool layout … question, if you were to incorporate this into a WordPress child theme, do you have any recommendations as to what files to modify or templates to configure to achieve similar photo layout ..??..
The only CMS I’ve ever used was Textpattern. And that’s… a while ago.
Awesome. This inspired me to see how it would do on my actual site. Like others, I added gutter, box-shadow and border before seeing the comments. I wanted mine linkable and hoverable, and to work in my 75% main content section (with Bootstrap 4). I stole the ul:after and border-radius suggestions. results: [ https://codepen.io/Gene/pen/LYYVPVm ]
There are some slight downsides to this :cover approach depending on your taste – it zoom-crops everything (tips to fix and :cover less?), and portrait photos are often half the size of landscape. For these reasons I ultimately decided to just spice up my own grid-based aspect-ratio gallery with what I learned.
I still love the idea and these minimum-code examples. It just takes creativity to apply them in the middle of sites with libraries and style baggage. I will keep a copy of this for use where it’s more appropriate! Compliment sandwich!
That was the goal
Cool and clean. I wrap this into a React function https://codepen.io/mjunaidi/pen/JjjJWoo
Hi
Nice layout.
How can I import all images from a folder and show them on the website with this Layout?
So when I add or delete a image in the folder -> the image is added/deleted in the website too.
Thanks for the inspiring example. I used this to create a wall of recent uploads to my Flickr account at https://www.rockland.dk/show/?post=412 . Nothing special. Pretty much copy/paste of your code, except it is a fixed width blog, so skipped the viewport optimizations. Just want to say thanks for sharing the example (and maybe I should look into the suggestions on using :after for the extra trailing li element)…
Check this image, and the same in your flex example
https://res.cloudinary.com/css-tricks/image/upload/f_auto,q_auto/v1568814785/photostream-photos/DSC05586_oj8jfo.jpg
You will see it is vertically chipped off. Is that expected ?
Hey! Yeah, that would be expected because the demo is using
object-fit: cover;
on the img element. That will make an inline image act like a CSS background image and clip where its container stops.To add to my last comment, the thing is notable in example at top of exactly this page !
Thanks for this.
But hey how do you add descriptions just below each image, thanks