Preventing Reflow

Published on (03-03-2017).

I saw this in my timeline yesterday:

Released css-aspect-ratio, a tiny module for preserving aspect ratios in pure CSS w/ custom properties. Take a look! https://t.co/CgUgv1ptaw

— Sérgio Gomes (@sergiomdgomes) March 1, 2017

This is a very clever use of pseudo-element as the box layout is dictated by the styling of ::before rather than the padding of the box. Using the “padding hack[1] on the container would set the proper aspect ratio, but that would break once the box reaches a max-width (or a min-width for that matter). And that’s exactly the problem Sérgio Gomes solves with his use of ::before.

After looking at Sérgio’s solution I thought we could do without the class and with a single input for the ratio value (relying solely on a single custom property). It also saves a few bytes, but that’s negligible I guess.

UPDATE: this solution now allows you to wrap images with their width and height attributes.

TL;DR:

The native width of the image below is 600px. Its container is styled with max-width:500px hence the image should be no more than 500px wide and only flexible below that threshold.

<div style="--aspect-ratio:600/400;">
  <img src="https://unsplash.it/600/400" alt="">
</div>

Browsers do not wait for the image to dimension the wrapper, they get its width and height from CSS.

Applying Ratios

If you ever struggle with finding the aspect ratio of an element, fear no more because this solution allows you to enter values in a variety of formats:

<div style="width:400px;--aspect-ratio:400/300;">
  <img src="https://unsplash.it/400/300" 
       alt="">
</div>
<div style="width:20em;--aspect-ratio:4/3;">
  <img src="https://unsplash.it/400/300" 
       alt="">
</div>
<div style="width:50%;--aspect-ratio:1.3333;">
  <img src="https://unsplash.it/400/300" 
       alt="">
</div>

Portrait vs. Landscape, how do I know?

It’s easy to differentiate portrait from landscape boxes by looking at the value of --aspect-ratio:

The CSS

The CSS is very straightforward:

[style*="--aspect-ratio"] > :first-child {
  width: 100%;
}
[style*="--aspect-ratio"] > img {  
  height: auto;
} 
@supports (--custom:property) {
  [style*="--aspect-ratio"] {
    position: relative;
  }
  [style*="--aspect-ratio"]::before {
    content: "";
    display: block;
    padding-bottom: calc(100% / (var(--aspect-ratio)));
  }  
  [style*="--aspect-ratio"] > :first-child {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
  }  
}

Note: It is best practice to style img with vertical-align:bottom but in case you don’t, and if you do not style them with display:block either, then you’ll get a gap below your images in browsers that do not pick up the rules inside @supports ().

Other Uses

In browsers that support custom properties, you can use this solution to:

<div style="--aspect-ratio:16/9">
  <iframe src="video.mp4"  
    frameborder="0"
    allowfullscreen></iframe>
</div>
<div style="--aspect-ratio:600/400;background:url(https://unsplash.it/600/400);background-size:cover;"></div>

Things to consider

[1] For what it is worth, I think it should be called the padding trick, not the "padding hack" because, countrary to popular belief, it does not "make use of some quirks in padding." The technique relies purely on specs. The hack is not the creation of the aspect ratio, but rather the fact that we stretch an inner box inside its container.