Old-fashioned masonry layout for older browsers

The masonry type layouts are gaining popularity from the time when Pinterest started to be a well-known inspirational website. Using the modern CSS we can create such a layout – grid and flexbox are a big help. Unfortunately, if you need to support older browsers, the grid and flexbox are no the way to go.

One of the possibilities is to calculate the grid element sizes and place them using absolute positioning inside the container. This is the way I chose when creating the old-fashioned masonry script. It is using jQuery (to simplify the code) and calculates the sizes and positions of the items client-side (in the browser).

Assumptions

  • the script should work on older browsers but also on the modern ones
  • the size of the masonry is related to the size of the container (this allows me to put the masonry on the whole page or to fill only a particular part of the website)
  • all of the columns are equal in width
  • the items are displayed from left to right – the first one in the first column, the second in the second column, and so on. Once the first row is filled, the shortest column is filled next
  • the positions and sizes are re-calculated on the browser resize

The solution

Following the above assumptions, I created a few lines of CSS and a few Javascript functions to handle the layout. The HTML piece is as simple as that:

<div class="container">
        <div class="masonry">
            <div class="masonry-item">
                <img src="photos/01.jpg">
                <span class="masonry-text">Photo 01</span>
            </div>
            <div class="masonry-item">
                <img src="photos/02.jpg">
                <span class="masonry-text">Photo 02</span>
            </div>
            [... and so on ...]
    </div>
</div>

The container is the element that defines the width of the whole masonry grid, the masonry div is the main element for styling and items are the pieces of the masonry layout. The text element is optional – for some images you may decide not to use it.

Of course, you should also load jQuery and the masonry Javascript file itself. Talking of which – there are a few configuration options to set (if you wish).

In the masonry.js file you will find a few lines of configuration:

var defaultWidth = 350;
var distance = 10;
var containerLookup = '.container';

The “default width” is used to calculate the number of columns to display – the image widths will take values close to this number. The “distance” is the size of the gutter between images and on the sides of the container. The “container lookup” is the jQuery selector pointing to the container. If your code follows the HTML provided, you don’t need to change this value.

How it works – CSS

Let’s take a look at the CSS part of the layout. The whole CSS file I used in my example contains the following set of styles (removed not relevant items in the copy below):

.masonry {
    position: relative;
}

.masonry-item {
    position: absolute;
}

.masonry-item img {
    width: 100%;
}

.masonry-item .masonry-text {
    position: absolute;
    bottom: 0;
    width: 100%;
    display: block;
}

The main masonry div is set to be positioned relative, which will allow us to properly use absolute positioning in the masonry items. The image will fill the whole item width – this way we will be able to calculate the item height using the image height. The text is displayed at the bottom of the image, over the image.

The full style sheet contains also some neat transitions, the images are scaled up on hover, the text is not visible by default (it is revealed on hover). This way the layout looks cleaner but also is more dynamic and attractive.

How it works – Javascript

The position of each element has to be calculated. We need to know how many columns will be displayed, what should be the width of each element, which column to place the element in. The above calculations are performed by the following function:

function positionGridImages() {
    var columnsHeight = [];
    var containerWidth = $(containerLookup).width();
    var numberOfColumns = Math.max(Math.round(containerWidth / defaultWidth), 1);
    var elementWidth = Math.round((containerWidth - ((numberOfColumns + 1) * distance)) / numberOfColumns);
    
    for (var i = 0; i < numberOfColumns; i++) {
        columnsHeight[i] = 0;
    }

    // set widths of all items to elementWidth
    $('.masonry-item').width(elementWidth + 'px');

    // set individual heights - for each item
    $('.masonry-item').each(function () {
        heightToSet = $(this).find('img').first().height();
        $(this).height(heightToSet + 'px');
    })

    // set positions - calculate left and top for each item
    $('.masonry-item').each(function () {
        columnToFill = findShortestColumn(columnsHeight, numberOfColumns);

        leftToSet = ((columnToFill * (distance + elementWidth)) + distance);
        topToSet = columnsHeight[columnToFill] + distance;

        $(this).css('left', leftToSet + 'px').css('top', topToSet + 'px');

        // save column height for the column that was filled with this item
        columnsHeight[columnToFill] += distance + $(this).height();
    })

    // set the size of the whole masonry
    $('.masonry').height(
        (findLongestColumnHeight(columnsHeight, numberOfColumns) + distance) + 'px'
    );

    savedWidth = containerWidth;
}

Each masonry item is placed in the “shortest” column, the first elements are placed from left to right and once the first row is filled, the shortest column is selected to place the next item. In the end, the whole masonry element size is calculated based on the longest column height.

Window resized?

The positioning function can be executed once, but what if the browser window gets resized? Modern browsers are equipped with the resize event, so we can respond and execute the calculations again. Because older browsers are not that smart, I used the simple interval and I’m checking if the container width changed:

var savedWidth = 0;

function checkMasonrySize() {
    if (savedWidth != $(containerLookup).width()) {
        positionGridImages();
    };
}

$(function () {
    setInterval(checkMasonrySize, 500);
});

The check function is executed every half a second, but you can call it also on other events – for instance, you can call it after the Ajax call that will add some items to the masonry. You can also call it on the content changes.

Summary

I’m almost certain that there are easier or more elegant ways to do the same. I will be happy to see your solutions and suggestions. Feel free to clone the repo and send me pull requests with your ideas. The repo is available on GitHub:

https://github.com/pdulak/oldFashionedMasonry

The code is on the MIT license, you can use it for the non-commercial and commercial projects for free, but without any warranty or liability from my side. Happy coding!