For the redesign of the Photo Section of Facebook we wanted to highlight some photos by making them bigger. It all started with the following mock by Andy Chung:
Layout
Alternated Blocks
My first shot at the problem was to make a fixed layout where we would alternate big images on the left and on the right.
With this scheme, you have one big image every 9. We started brainstorming about putting the one with the most number of likes and comments there. However, this felt to be very arbitrary.
Emily Grewal, the Product Manager of the project wanted something better: being able to make any (and all) image bigger. Automatic selection of images to be bigger was also discarded as we wanted the user to feel in control of this space.
Big and Small Blocks
In the next iteration, I tried to give more control by reducing the size of the blocks. Now it is either one big image or 4 small ones.
One constraint we had at the time was that the columns could start at an arbitrary height. With this layout, we can make the block display: inline-block;
and the browser is going to reorder them automatically as the columns initial height are changing.
However, this wasn't all shiny. One nasty side effect is the fact that the ordering of two small blocks next to each other is awkward. Instead of being from left to right they are in zig-zag. You could linearize those but you would lose the layout from CSS.
Smaller Small Blocks
The next (and final) idea is to shrink the small blocks from 4 to 2 elements. It solves our zig-zag issue and feels more natural.
Algorithm
The idea behind the algorithm is to have temporary blocks that serve as buffer. We iterate over all the input elements and put them into the temporary block of the corresponding size. Once a temporary block is full, we add it into the grid in the smallest column.
Here's an example where we try to layout AbcdEf:
And there's the pseudo-code implementation of the algorithm:
function layout(elements) { var columns = [new Column(/*height */ 0), new Column(/*height */ 0)]; var stash = []; elements.forEach(function (element) { if (element.isBig()) { var column = columns.getSmallestColumn(); column.renderBigBlock(element); column.height += 2; } else /* element.isSmall() */ { stash.push(element); if (stash.length === 2) { var column = columns.getSmallestColumn(); column.renderSmallBlock(stash[0], stash[1]); column.height += 1; stash = []; } } }); if (stash.length > 0) { var column = columns.getSmallestColumn(); column.renderSmallBlock(stash[0], stash[1] /* can be undefined */); column.height += 1; } } |
Conclusion
Check out the Demo!
This is the layout algorithm we ended up using for Facebook photos. We found a way to let the user make all the images he wants bigger minimizing the risk of the result being ugly.
Pros:
- Can make any/all image bigger
- No holes
- Order is mostly respected
- Need to store only two sizes per image
Cons:
- All images have the same dimension
- Cropping required to display both landscape and portrait images
- Only works for a number of column that is a multiple of 2
If you want to know how reordering works, read the follow-up article 😉