Overlaps and Whitespace

While working on the cover page to display all my photo stories I’ve been experimenting with overlaps and whitespace using CSS Grid. In this article I want to share my learnings to combine this layout with responsive images. This is how it looks like:

A landscape photo aligned to the right overlapping a square photo aligned to the left. Before I go into the implementation I want to point to the great video series Layout Land by Jen Simmons – there is even one video dedicated to overlaps. She explains CSS Grid way better than I ever could, so for more in depth knowledge I highly recommend her and Rachel Andrew’s work.

CSS Grid

But back to this example. The HTML shows a landscape (aspect ratio 3:2) and a square (aspect ratio 1:1) image stacked on top of each other:

<div class="tiles">
  <img class="tiles-landscape" src="landscape.jpg">
  <img class="tiles-square" src="square.jpg">
</div>

To create the layout above I’ve added the following CSS:

.tiles {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
}

.tiles-landscape {
  grid-column: 1 / span 4;
  grid-row: 1 / span 3;
}

.tiles-square {
  grid-column: 4 / span 3;
  grid-row: 2 / span 1;
}

For the wrapping element tiles I set up a grid of six columns. The landscape image tiles-landscape starts at the first column covering four columns and the first row covering three rows. The square image tiles-square starts at the fourth column covering three columns and second row covering one row. So the images overlap on the fourth column. I did not set the height of the rows, this gets calculated automatically by the content size. One thing I love about the CSS Grid layout: if it isn’t supported none of these properties will work and we have a nice fallback to vertically stacked images.

Srcset & Sizes

But this layout will be seen on different screens – therefore I provide each photo in multiple sizes. For this site I’ve decided to create bounding boxes of 1200, 848, 600, 424, 300, 212 and 150 pixels (the idea is that each bounding box about half the size of the previous one). All these sizes are passed as srcset. The second attribute sizes tells the browser at what size the image will be rendered in the current layout. Let’s start with the landscape photo: it covers 4 of 6 columns, so in viewport units that’s 67vw (two thirds of 100vw). As the name says: these viewport units refer to the width of the viewport and not the container. The container element tiles in my layout has a maximum width of 742 pixels. So for any size above that the size should be set in pixels and not viewport units: two thirds of 742 pixels are 495 pixels. For the square photo the calculation is the same: three of six columns, so 50vw and 371 pixels above 742 pixels.

<div class="tiles">
  <img class="tiles-landscape"
       src="landscape-848.jpg"
       srcset="landscape-1200.jpg 1200w,
               landscape-848.jpg 848w,
               landscape-600.jpg 600w,
               landscape-424.jpg 424w,
               landscape-300.jpg 300w,
               landscape-212.jpg 212w,
               landscape-150.jpg 150w"
       sizes="(min-width: 742px) 495px, 67vw">
  <img class="tiles-square"
       src="square-848.jpg"
       srcset="square-1200.jpg 1200w,
               square-848.jpg 848w,
               square-600.jpg 600w,
               square-424.jpg 424w,
               square-300.jpg 300w,
               square-212.jpg 212w,
               square-150.jpg 150w"
       sizes="(min-width: 742px) 371px, 50vw">
</div>

As src I picked the width that would work best if neither CSS Grid nor srcset/sizes is supported. As the photos would be shown over the full width the size with 848 pixels is a good fit.

Fallback

There is only one last “problem”: what if the browser supports srcset/sizes, but not CSS Grid? In that case the image will be shown over the full width, but the sizes directive tells the browser to maybe load the wrong file. To avoid this I’ve added the following JavaScript:

if (!CSS.supports('display', 'grid')) {
  Array.prototype.forEach.call(document.querySelectorAll('img[sizes]'), function(element) {
    element.setAttribute('sizes', '(min-width: 742px) 742px, 100vw');
  });
});

It loops through all img elements with the sizes attribute and replaces it with a rule to cover the full width if display: grid is not supported.