Maintain Aspect Ratio Mixin

This article from July 2013 describes a method of using psuedo elements to maintain an elements aspect ratio, even as it scales.

Here's a Sass mixin that simplifies the math a bit.

@mixin aspect-ratio($width, $height) {
  position: relative;
  &:before {
    display: block;
    content: "";
    width: 100%;
    padding-top: ($height / $width) * 100%;
  }
  > .content {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
  }
}

The mixin assumes you'll be nesting an element with the class of content inside your initial block. Like this:

<div class="sixteen-nine">
  <div class="content">
    insert content here
    this will maintain a 16:9 aspect ratio
  </div>
</div>

Using the mixin is as easy as:

.sixteen-nine {
  @include aspect-ratio(16, 9);
}

Result:

.sixteen-nine {
  position: relative;
}
.sixteen-nine:before {
  display: block;
  content: "";
  width: 100%;
  padding-top: 56.25%;
}
.sixteen-nine > .content {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}

Demo

Here's a demo showing the above code in action. The animation is added to show the element maintaining the assigned aspect ratio as it resizes.

See the Pen Maintain Aspect Ratio Demo by Sean Dempsey (@seanseansean) on CodePen.

Thanks to Sean Dempsey (@thatseandempsey) for this one!

Comments

  1. User Avatar
    Matt
    Permalink to comment#

    It’s a nice technique. The only issue is that if the content of the box expands for some reason, the height doesn’t adjust. So if the box is filled with a few more words than what fits, or a user increases the text size, the text might overflow out of the box. Would be nice to have the best of both worlds: maintain aspect ratio if possible, but also expand if needed.

    • User Avatar
      Jakob E
      Permalink to comment#

      Hi Matt,

      You can do a “Maintain aspect ratio or fit to content” version like this:
      …and kill the wrapper :-)

      pen: http://codepen.io/jakob-e/pen/LEdWNB

      HTML
      <div>
        <p>Maintain aspect ratio or fit to content</p >
      </div>
      
      
      SCSS
      @mixin aspect-ratio($ratio-or-width, $height: null) {
        $padding: if($height, 
                     percentage($height/$ratio-or-width), 
                     percentage(1/$ratio-or-width)
                  );
        &:before { content:''; float: left; padding-bottom: $padding;  }
        &:after  { content:''; display: table; clear: both;  } 
      }
      
      @mixin aspect-ratio($ratio-or-width, $height: null) {
        $padding: if($height, percentage($height/$ratio-or-width), percentage(1/$ratio-or-width));
        &:before { content:''; float: left; padding-bottom: $padding;  }
        &:after  { content:''; display: table; clear: both;  } 
      }
      
      
      div {
        @include aspect-ratio(16,9);
        // ...or by simple ratio
        // @include aspect-ratio(1.777777778);  
      
        // Not important styling
        background:purple;
        width:33%;
        p { font:12px sans-serif; color:#fff; padding:1rem; margin:0; }
      }
      
      
      
    • User Avatar
      Matt
      Permalink to comment#

      Hi Jakob,

      That is a very clean solution! Very nice, thanks

    • User Avatar
      Augustin
      Permalink to comment#

      Jakob’s solution works on compilation time, which means if the ratio is independant from screen size. But that’s still no true responsive aspect-ratio, allowing the stretch to work if the height is the limiting dimension.
      Any other solution to suggest maybe?

    • User Avatar
      Robert

      Hi Jakob, nice work on this approach. I had been using it and came across an issue in FF today. If the element with the aspect ratio applied has display:flex;, it won’t work in FF.

      I’ve been trying to find a fix but haven’t been able to…yet.

    • User Avatar
      italodr
      Permalink to comment#

      Robert, I’ve run this issue with flex and Firefox, and one solution I’ve found was to wrap the apect-ratio element with a div, so the flexbox will affect this wrapper instead of the aspect-ratio element itself. That will fix the issue in FF
      (BTW: sorry for my english)

      I just forked your pen

  2. User Avatar
    Ben
    Permalink to comment#

    As a related aside I wrote this sass mixin for having a css background image maintain it’s aspect ratio.
    This allows you to not download/display the image on smaller (mobile) screens, via breakpoints, but have it behave like an html < img > and maintain it’s aspect ratio responsively when you do show it

    // Calc image intrinsic aspect ratio
    @function get-img-aspect-ratio($img-url){
    $img-height: image-height(“#{$img-url}”);
    $img-width: image-width(“#{$img-url}”);
    $aspect-ratio: $img-height / $img-width;
    
    @return $aspect-ratio;
    }
    
    @mixin intrinsic-ratio-bg-img($img-url){
    background-size: contain; // note: could also use ‘cover’ but it is less supported.
    background-position: top right;
    background-repeat: no-repeat;
    padding-top: percentage(get-img-aspect-ratio($img-url));
    height: 0px !important;
    display: block;
    }
    
    • User Avatar
      zolu
      Permalink to comment#

      Could you please tell me how to use this mixin for a div with css background-image ?

    • User Avatar
      Ben
      Permalink to comment#

      @Zolu

      div{
      @include intrinsic-ratio-bg-img(“/path-to-image/image.jpg”)
      }

      Is that what you were asking?

  3. User Avatar
    Max Rocket
    Permalink to comment#

    Genius! Thanks, this saved me hours of frustration!

  4. User Avatar
    Kay
    Permalink to comment#

    This helped me solve an issue with fixing the height of a child element in a bootstrap column to ensure contiguous distribution of and columns in a masonry layout. Thanks much!

  5. User Avatar
    shawn
    Permalink to comment#

    A friend showed me this trick and it seems to work well while I was developing in Chrome. But then when I went to test it in Firefox it didn’t work. Any clue why? It seems the the before padding is not being respected.

    • User Avatar
      shawn
      Permalink to comment#

      It turns out that my parent div had the style “display:flex” and I fixed it by changing the style to “display:block”.

    • User Avatar
      zazzy

      this works! much appreciated :)

    • User Avatar
      Max
      Permalink to comment#

      I love you guys for this fix!! Going crazy over Firefox here :D

  6. User Avatar
    Thora
    Permalink to comment#

    This worked great! I’m not a programmer, so I’m putting my site together kind of piecemeal, and adding things I find here and there, and trying to figure them out. This was fully understandable! I used this to stop my slideshow from jumping on the top of the page. Awesome!!

    Thank you.

    Thora

  7. User Avatar
    sanbor
    Permalink to comment#

    Very cool! You can also just use css calc() to compute that if you are not using a css preprocessor. Working example: http://codepen.io/anon/pen/bBLGWL

    Don’t forget to resize the viewport!

    <!DOCTYPE html>
    <html>
    
      <head>
        <link rel="stylesheet" href="style.css">
        <style>
          .Image {
            background-image: url(http://placehold.it/350x150);
            background-repeat: no-repeat;
            background-size: contain;
            position: relative;
            width: 100%;
            max-width: 350px;
          }
          .Image:before {
            display: block;
            content: "";
            width: 100%;
            padding-top: calc(((150/350) * 100%));
          }
          .Image > .content {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
          }
        </style>
      </head>
    
      <body>
        <div class="Image"></div>
        <div class="Image"></div>
        <div class="Image"></div>
        <div class="Image"></div>
      </body>
    
    </html>
    
  8. User Avatar
    Michel Joanisse
    Permalink to comment#

    There’s a quirky behaviour when using this method and applying either a bottom margin or border to the parent element. If it’s as little as say 1 pixel, you’ll see in some cases Safari won’t render the property respectively, resulting in no space or border. Resize your window in Safari to see what I mean.

    Demo: http://codepen.io/mjoanisse/pen/zZNOGr/

    To get around it, you can use the ::after pseudo-class and absolutely position the element so that it’s fixed to the bottom of your box. That said, it’s hacky and has it’s limitations.

    Curious to hear from someone who might have a clever solution to this problem.

  9. User Avatar
    Andrea

    I’d like to have a DIV that resizes keeping the ratio inaltered also when the height of the container changes.
    Does anybody knows how to do it?
    Thank you in advance!

Submit a Comment

Posting Code

You may write comments in Markdown. This makes code easy to post, as you can write inline code like `<div>this</div>` or multiline blocks of code in triple backtick fences (```) with double new lines before and after.

Code of Conduct

Absolutely anyone is welcome to submit a comment here. But not all comments will be posted. Think of it like writing a letter to the editor. All submitted comments will be read, but not all published. Published comments will be on-topic, helpful, and further the discussion or debate.

Want to tell us something privately?

Feel free to use our contact form. That's a great place to let us know about typos or anything off-topic.

icon-anchoricon-closeicon-emailicon-linkicon-logo-staricon-menuicon-nav-guideicon-searchicon-staricon-tag