Layout Objects like a Brickmason

09 Jan 2020

First Entry Preamble

I'm finally documenting the code I write whooo. So I'm gonna start with documenting about my website as I build it so I never have to complain to myself that, “wow should have documented as I made this”.

Pictures layed out in mason-brick style on my site.

So with CSS Grid & some JS we'll be able to get this effect that's in Pinterest.

HTML & CSS Grid

So first we'll just grab a bunch of images and place them in a grid as such:

        
        <div class="maingrid">
            <div class="brick"><img src="dog.jpg"/></div>
            <div class="brick"><img src="banana.jpg"/></div>
            <div class="brick"><img src="goku.jpg"/></div>
            <div class="brick"><img src="dog.jpg"/></div>
            <div class="brick"><img src="dog.jpg"/></div>
            <div class="brick"><img src="goku.jpg"/></div>
            <div class="brick"><img src="goku.jpg"/></div>
        </div>
    

And really quickly styling them with some CSS...

        
        .maingrid { 
            /* width: 90vw; */
            display: grid; 
            grid-template-columns: repeat(2, 1fr);
        }
    

Just to make sense of what I'm looking at, I'm gonna alternate the colors of the divs:

        
        .maingrid div:nth-child(2n) { background-color: salmon;}
        .maingrid div:nth-child(2n + 1) { background-color: violet;}
        .maingrid div img { width: 300px;}
    

In regular CSS grid, each div takes the height of the tallest image in the row.

So as you can see, there's all this extra height added to the duck divs because it's on the same row as Goku.

Note: In grid-template-columns, the CSS function repeat(2, 1fr) simply means make two equally-sized columns that take up the size of the grid, which in this case is the size of the page. So if .maingrid has a width of half the page (50vw) then each column is 25vw (or at least something like that).

To get this layed out in brickmason fashion we'll start by adding a little more CSS to the grid container:

        
            .maingrid { 
                display: grid; 
                grid-template-columns: repeat(2, 1fr);
                grid-auto-rows: 15px;
                grid-row-gap: 12px;
            }
    

And we'll get something along the lines of this:

Now, why would we want to have all the images overlap like this? Well in essence, we're going to use Javascript to calculate how many 15px-tall rows each div (or “brick”) should take up. This way, we can dynamically set the images based on their heights.

Sweet Vanilla JS

What we're trying to do here is

    For each image:
  • find it's height
  • divide height of image by the height of grid-auto-rows plus grid-gap

We use jQuery in these parts, so that's what the following code is gonna look like. First, I'll get the main parts of this script:

    
    function findAllHeights() {
        //get main grid to access values later
        const grid = $(".maingrid");

        // get all the 'bricks' in the main grid
        let allbricks = $(".brick");

        //for each brick, find how many rows tall it is
        allbricks.each( function() {
            sizeHeight(this, grid)
        });
    }

    function sizeHeight (brick, grid) {
        //find out how many rows each brick is
    }

    $( window ).on("load") { findAllHeights };
    $( window ).on("resize") { findAllHeights };
    

The code above selects the grid and all the divs that have the images inside. Then we will loop through each .brick and calculate how many rows high it should be. Finally, this function should be called when the page loads and everytime you resize it.

Now, all that's left is the sizeHeight function which is fairly simple as well:

    
    function sizeHeight(brick, grid) {  
        let rowunit = parseInt($( grid ).css( "grid-auto-rows" ))
        let gap = parseInt( $( grid ).css( "grid-row-gap" ))

        let imgheight = parseInt($( 'img', brick ).height())
        let rowspan = Math.ceil((imgheight)/(rowunit + gap))

        $( brick ).css( "grid-row-end", "span " + rowspan);
    }
    

In this function, we query the grid and we save the value of grid-auto-rows (which is 15px) and grid-row-gap (which is 12px) as variables. You also save the height of the image within the div, not the brick div itself. (That's what $( 'img', brick) means.) Then divide. After you set the grid-row-end (the CSS style that tells you how many rows the object should span) of each image, everything should look like:

Nice mason brick arrangement!

And I think that's it. :)