Fixed-height elements with an intrinsic ratio

The padding-bottom technique is perfect when you need an HTML element to have an intrinsic ratio, provided the element is 100% wide. But it doesn’t work the other way around, when you want to set the element’s width as a percentage of its height. At least not without a helping hand from JavaScript.

Here is a real world example of when this might be useful, an image carousel where the slides have a fixed height and variable widths:

At different viewport widths the carousel is taller, so we can’t simply hardcode each slide’s width. Before today I would have told you that the only reliable way of scaling the slides was to pass their aspect ratios to JavaScript, calculate the width of each slide relative to its height, then resize them accordingly. And to do that for every slide, every time the viewport is resized. It would be much simpler if the slides could be scaled once by CSS, but to achieve that we need a way to give each slide a relative width.

The solution: a canvas shim

My solution is to use a transparent canvas element to force the aspect ratio. The key to this technique is the fact that if you define width and height attributes for a canvas, then it keeps the same aspect ratio when you change its height with CSS (the same is not true of the img element, which is is why I didn’t use a transparent PNG or GIF as a shim).

<div class="intrinsic-ratio">
  <canvas class="ratio-shim" width="200" height="100"></canvas>
  <img src="my-image.jpg">
</div>
.intrinsic-ratio {
  position: relative;
  display: table-cell;
  width: auto;
  height: 200px; /* height can be any value */
}
.ratio-shim {
  height: 100%;
}
/* The content could be anything. In this case an img */
.intrinsic-ratio img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

So yeah, an extra HTML element is required for this to work, but that’s not so awful when you consider that the alternative is measuring and resizing the element with JavaScript.

You might also notice that the wrapping element has its display property set to table-cell. This makes it shrink-wrap to the width of its content. display: inline-block would work too, as would floating the element or giving it absolute positioning.

I don’t imagine that I am the first person to come up with this technique, but it’s not one I’ve seen in the wild so it might be worth keeping in the toolbelt until browsers have native support for aspect-ratio.

Demo

View a demo on CodePen

Notes

The CSS padding technique doesn’t work when an element’s width is unknown because percentage-based padding is relative to the width of the containing element, not its height. When you only know the height of the element, setting padding-left or padding-right as a percentage will produce inconsistent results.