Maintaining Ratio Of Elements: All The Methods

It's quite often that elements such as an images or videos are required to maintain their ratio at different scales; especially when working with fluid containers that alter their width across various viewport sizes and breakpoints.

There’s a few tricks in CSS (and perhaps JavaScript) to help with this, some of which have been making the rounds for years. So why write a post about it? It’s interesting to compare methods, and I’ve seen front-end developers approach challenges like this in different ways. I’m sure there’s a few more obscure methods I havn’t listed below, but here are the ones I’m aware of.

Uncle Dave’s Padded Box

This now rather well-known method is based on intrinsic ratios. Basically, the aspect ratio is maintained by a percentage value applied to the top or bottom of the element with the padding attribute. This value is relative to the width of the parent element. For example, if the width of the element is 100%, a padding value of 75% would give you 4:3. If the width of the element is 20%, a padding value of 15% would give you the same ratio. So in CSS:

.element {
    height: 0;
    width: 100%;
    padding-bottom: 75%; /* 4:3 */
}

Once you have the desired ratio, you could then absolute position content within the element, and use background-size: cover to fill the space with a background image. like so:

.element {
    position: relative;
    height: 0;
    width: 20%;
    padding-bottom: 15%; /* 4:3 */
}

.element__content {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-image: url(url/to/img.jpg);
    background-size: cover;
}

View Demo

An alternative way to force the image to fill out its container (with limited support) would be to an inline the img in HTML and apply object-fit: cover or object-fit: fill in CSS.

Pseudo Padding

An evolution of the previous method; padding could be applied to a pseudo element of a container instead. That way, padding doesn’t need to be adjusted every time the width changes.

.element {
    width: 20%; /* desired width */
    position: relative;
}

.element:before {
    display: block;
    content: "";
    padding-top: 75%; /* 4:3 */
}

View Demo

The method above can also be applied as a handy Sass mixin.

Sass

If you have Sass at your disposal, a pretty simple way to dynamically change the height of an element would be to set up a $width variable on the pseudo element and do a ratio multiplication on the padding value.

.element {
    position: relative;
    width: 20%;
}

.element:before {
    $width: 100%;
    display: block;
    content: "";
    padding-bottom: ($width * 0.75);
}

View Demo

Viewport Units

Viewport units such as vw and vh currently have limited support, but they could enable you to scale and maintain an element’s ratio relative to the viewport.

.element {
    width: 20vw;
    height: 15vw;
}

View Demo

Blank Image

One method that’s not semantically perfect, but pretty fail safe, is using a small transparent png image with the desired ratio baked into its canvas. This image is set to 100% width with automatic height, and then placed within a container of a desired width. Content could be absolute positioned above the image.

<div class="element">
    <div class="element__content">
      <!-- element content goes here -->
    </div>
    <img class="element__ratio" src="path/to/1x1.png">
</div>

.element {
    width: 20%;
    position: relative;
}

.element__content {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.element__ratio {
    width: 100%;
    height: auto;
    float: left;
}

View Demo

One minor drawback of course is that additional HTTP request for the image which could impact load performance. Even so, you could keep the filesize down to a minimum. A 1x1 pixel png would provide a 1:1 ratio.

JavaScript

With JavaScript it is possible to set the height of elements according to their width. For example, with event listeners on load and resize events, you could loop through multiple elements and dynamically apply heights based on a simple ratio calculation. It might look like the following:

/* Define the class of the elements */
var elems = document.querySelectorAll('.element');

/* Method to get the height of an element */
var getWidth = function(elem) {
    return Math.max(elem.scrollWidth, elem.offsetWidth, elem.clientWidth);
};

/* Event Handler */
var handler = function() {

  /* Loop through all the elements */
  for (var i = 0, len = elems.length; i < len; i++) {
    
    /* Calculate the desired height based on width */
    /* Adjust multiplication according to ratio */
    var autoHeight = (getWidth(elems[i]) * 0.75) + 'px';
    
    /* Set the height of each element */
    elems[i].style.height = autoHeight;
  }

};

/* Event Listeners */
window.addEventListener('DOMContentLoaded', handler, false);
window.addEventListener('resize', handler, false);

View Demo

Another interesting possibility with JavaScript would be to make calculations and leverage transform: scale();, as Chris Coyier demonstrates. The potential downside to this is you are scaling down the entire element, including its contents, which may or may not be an issue depending on the requirement of the project.

Final Thoughts

In an ideal world, a ratio attribute baked into the CSS spec would be the ultimate tool and it’d be awesome to be able to specify a ratio value that dynamically controls the width or height if one of those values is declared. With SVG it is possible to leverage preserveAspectRatio to indicate whether or not to force uniform scaling.

While maintaining ratio can still be useful on certain projects, the responsive and fluid nature of the web has forced us to also consider flexible ratios and dimensions, filling available spaces, or matching heights of elements. Every project is different and the various methods in this post and beyond arn’t one-size-fits-all.

By Christian Miller  |  Follow me on Twitter