5

Given a parent HTML div element that can resize both vertical and horizontal, I want the child div to maintain aspect ratio (for example, 16:9), be centered in and restrained by the edges of the parent. No JS please.

enter image description here

Is this possible with CSS alone? (The top "wider" parent image example is pillarbox, and the bottom "narrower" is letterbox.)

Note: the parent will be smaller than the actual browser viewport in most cases, so don't judge your answer (for example) bases on the width or height of the browser viewport. This should be able to stand alone at any given parent / child size.

arxpoetica
  • 4,841
  • 3
  • 30
  • 35
  • None of [these](https://stackoverflow.com/questions/1495407/maintain-the-aspect-ratio-of-a-div-with-css) do what you're asking? – Chris W. Dec 26 '18 at 19:48
  • @ChrisW. as far as I can tell, they all rely on responsive *width*. This is specifically asking for both height and width contained aspect scaling. Is there a particular response in the link you posted that includes both working? – arxpoetica Dec 26 '18 at 21:01
  • Pretty sure I saw one in there that follows [the basics](https://www.w3schools.com/howto/howto_css_aspect_ratio.asp) but I do have a bad habit of speed skimming sometimes. – Chris W. Dec 26 '18 at 22:30
  • @ChrisW. Maybe I need to clarify my question. The link you provided to "the basics" doesn't answer the question. It answers letterboxing responsive aspect ratio maintenance, but not pillarboxing. I need both. Padding-bottom/-top only works for letterbox. – arxpoetica Dec 27 '18 at 04:29

6 Answers6

3

Solution

Nearly four years later, @container queries and aspect-ratio provide a very nice solution!

<div class="resize-at-will">
  <div class="maintain-aspect-ratio">
    ...
  </div>
</div>

<style>
  .resize-at-will {
    container-type: size;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .maintain-aspect-ratio {
    aspect-ratio: 16 / 9;
    width: 100%;
    background-color: #ff3e00;
  }
  @container (min-aspect-ratio: 16 / 9) {
    .maintain-aspect-ratio {
      width: auto;
      height: 100%;
    }
  }
</style>

Caveats

Note, it's not responsive to the viewport, but actually responsive to the parent div with the container-type: size; CSS declaration. If you want responsive to viewport, just use @media ([min/max]-aspect-ratio: x / x) { ... }

Also note, container queries are in most modern browsers, but not yet Firefox. However, there's a polyfill: https://developer.chrome.com/blog/cq-polyfill/#the-container-query-polyfill

Further Information

arxpoetica
  • 4,841
  • 3
  • 30
  • 35
1

If you're willing to use an iframe for this, I have a creative solution.

Font size can be changed dynamically relative to the viewport size. You want to resize the container and not the window, so for this to work we'll have to wrap everything with an iframe which has a viewport of its own, and resize the iframe.

The idea is to set the width and height of your element with em units, and set the font size dynamically.

We have 2 scenarios, a wider container or a higher container, to separate it we'll use media query:

max-aspect-ratio: 16/9

1. A higher container

Since we want the div to keep 16:9 ratio we'll set the div's dimensions to

width: 16em;
height: 9em;

In order for it to take 100% width we'll set the the font-size to 100/9 = 11.111

font-size: 11.111vh

2. A wider container

Once our media query kicks in and the container is wider so we'll change the font-size to be based on the viewport width and we want it to take 100% width so -> 100/16 = 6.25

@media (max-aspect-ratio: 16/9) {
  .ratio-wrapper {
    font-size: 6.25vw;
  }
}

Now we can add another div for the content and set whatever font size we want inside.

var iframe = document.getElementById('iframe');
var content = "<html>" +
  "<head>" +
  "<style>" +
  "body," +
"html {" +
"  padding: 0;" +
"  margin: 0;" +
"}" +
".ratio-wrapper {" +
"  background-color: #bada55;" +
"  font-size: 11.111vh;" +
"  width: 16em;" +
"  height: 9em;" +
"}" +
".center {" +
"  width: 100%;" +
"  height: 100%;" +
"  display: flex;" +
"  justify-content: center;" +
"  align-items: center;" +
"}" +
".content {" +
"  height: 100%;" +
"  display: flex;" +
"  justify-content: center;" +
"  align-items: center;" +
"  font-size: 20px;" +
"}" +
"@media (max-aspect-ratio: 16/9) {" +
"  .ratio-wrapper {" +
"    font-size: 6.25vw;" +
"  }" +
"}" +
  "</style>" +
  "</head>" +
  "<body>" +
    "<div class='center'>" +
     "<div class='ratio-wrapper'>" +
      "<div class='content'>It Works!</div>" +
     "</div>" +
    "</div>" +
  "</body>" +
  "</html>";
iframe.src = 'data:text/html,' + encodeURIComponent(content);
.cont {
  width: 300px;
  height: 300px;
  border: 1px solid black;
  resize: both;
  overflow: hidden;
  padding: 0;
  margin: 0;
}
<iframe id="iframe" src="about:blank" class="cont" scrolling="no"></iframe>

Note that I'm only using JS to add the content to the iframe, so it will work here, you can just set a href to your inner content and no JS is needed. I made the iframe resizable, so you can play with it easily, alternatively you can give it a responsive size, it works too.

Itay Gal
  • 10,706
  • 6
  • 36
  • 75
0

You can do that with CSS only using a Canvas combined with media queries:

<!DOCTYPE html>
<html>
  <head>
    <style>
      html, body {
        height: 100%;
      }
      .container {
        position: absolute;
        display: inline-block;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      .container, .aspect-ratio-canvas {
        @media (min-aspect-ratio: 16/9) {
          height: 100%;
        }
        @media (max-aspect-ratio: 16/9) {
          width: 100%;
        }
      }
      .aspect-ratio-canvas {
        padding: 0;
        margin: 0;
      }
      .content {
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        border: 1px solid black; // For demonstration
      }
    </style>
  </head>
  <body>
    <div class="container">
      <canvas class="aspect-ratio-canvas" width="16" height="9"/>
      <div class="content">
        Test with anything here
      </div>
    </div>
  </body>
</html>

You can replace the canvas with an image having the same aspect ratio if you wish.

This solution is based on this answer on another thread: https://stackoverflow.com/a/14911949/1581466

Romain F.
  • 766
  • 10
  • 15
0

After an exhausting search, I have come across this article, which does the trick with clever use of vw and vh units:

#ratio-16-9 {
  /* constrain to 16:9 ratio */
  width: 100vw; 
  height: calc(9/16 * 100vw);
  max-height: 100vh;
  max-width: calc(16/9 * 100vh);
  
  /* center */
  margin: auto;
  position: absolute;
  top: 0; bottom: 0; left: 0; right: 0;
  
  /* irrelevant */
  background-color: pink;
  text-align: center;
}
<div id="ratio-16-9">
  I stay 16:9 at all times while spanning as large as possible!
</div>

Notes:

  1. I swapped the hardcoded values in the original code with calc statements for readability reasons.
  2. This example is for 16:9 ratio. To apply another ratio, simply change 16s and 9s in calc statements accordingly.
starikcetin
  • 1,391
  • 1
  • 16
  • 24
-1

I believe this gets you what you want very simply:

<!DOCTYPE html>
<html lang="en-us">

<head>
    <meta charset="UTF-8">
    <title>
        Title
    </title>

</head>

<body>
    <div class="parent">text
    </div>

    <style>
        .parent {
            background-color: green;
            width: 100%;
            height: calc(100vw/1.77777);
        }
    </style>
</body>

</html>

The HTML and CSS are, of course, just to show it working. The important bit is that you simply determine what width you want (say, 100%) and then do a simple calculation to get the corresponding height to make it 16:9. In this case, that is 100% of the Viewport Width divided by 1.77777 to make it 16:9.

NOTE: This solution simply sets the height to a fraction of whatever you choose for width. If you don't want 100vw for width then you just substitute whatever you're using to designate the width (pixels, percent, what-have-you).

Desmond Mullen
  • 159
  • 1
  • 8
-1

You want this css

object-fit: contain

https://www.w3schools.com/css/css3_object-fit.asp

  • `object-fit` only applies to `` or ` – arxpoetica Mar 31 '20 at 19:28