36

I have a set of elements and require that their minimum width be equal to their height, but the height is not explicitly set. Currently I am able to achieve this by setting the css min-width property via jQuery:

$(document).ready
(
    function()
    {
        $('.myClass').each
         (
             function() {$(this).css('min-width', $(this).css('height'));}
         );
    }
);  

Is it possible to specify this behaviour directly in the css?

Here is a jsfiddle demonstrating what I am trying to achieve: http://jsfiddle.net/p8PeW/

verdesmarald
  • 11,646
  • 2
  • 44
  • 60
  • 1
    This question is for setting _width_ based on _height_. If you need _height_ based on _width_, check out [Maintain the aspect ratio of a div with CSS](https://stackoverflow.com/q/1495407/691281). – John Mellor Jan 26 '19 at 14:03
  • 1
    @verdesmarald Could you change the accepted answer by any chance? – Kerwin Sneijders Feb 14 '19 at 20:14
  • 1
    **NEW CORRECT SIMPLE ANSWER IS ```aspect-ratio widthnumber / heightnumber;```.** It works in both ways. For a demo see https://stackoverflow.com/a/68073761/14824067 – LuckyLuke Skywalker Mar 04 '22 at 14:30

10 Answers10

89

This is actually possible without JS!

The key is to make use of an <img>, which is a replaced element, and hence if you only set the height, it's width will be automatically set in order to preserve the intrinsic aspect ratio of the image.

Hence you can do the following:

<style>
  .container {
    position: relative;
    display: inline-block; /* shrink wrap */
    height: 50vh; /* arbitrary input height; adjust this */
  }
  .container > img {
    height: 100%; /* make the img's width 100% of the height of .container */
  }
  .contents {
    /* match size of .container, without influencing it */
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
  }
</style>

<div class="container">
  <!-- transparent image with 1:1 intrinsic aspect ratio -->
  <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
  <div class="contents">
    Put contents here.
  </div>
</div>

If you wanted .contents to have a 4:3 aspect ratio, then you would instead set the height of the img to 133%, so the width of the img and hence of .contents would be 133% of the height of .container.

Live demo of 1:4 aspect ratio: https://jsbin.com/juduheq/edit (with additional code comments).

Another live demo, where the height of the container is determined by the amount of text in a sibling element: https://jsbin.com/koyuxuh/edit

In case you're wondering, the img data URI is for a 1x1 px transparent gif). If the image didn't have a 1:1 aspect ratio, you'd have to adjust the height values you set correspondingly. You could also use a <canvas> or <svg> element instead of the <img> (thanks Simo).

If instead you want to set element height based on width, there's a much easier trick at Maintain the aspect ratio of a div with CSS.

John Mellor
  • 12,572
  • 4
  • 46
  • 35
  • 3
    this is a fantastic solution.. this should be the answer! Thanks @John – haxxxton Feb 06 '14 at 06:28
  • 2
    Can anybody get this working via e.g. `.container:first-child::before { ... }`? Would probably be very helpful to have a solution _using only css_! – Matteo B. Sep 20 '14 at 12:06
  • 3
    This method can be made a bit easier to use with a ``, as it's transparent by default and you can set its width/height arbitrarily to create an image of the desired aspect ratio, without ever having to draw on it. Note that you may have to set `padding: 0` and `margin: 0` on your canvas or image, or it might want to take more space than supposed to. – Simo Kinnunen Apr 22 '15 at 23:35
  • 1
    This doesn't seem to work if the container doesn't have an absolute value, but a percentage? Modifying the pastebin to set 20% as the container's height doesn't work – Sébastien Tromp Nov 05 '18 at 21:02
  • 1
    @SébastienTromp Percentages work fine. In your case the container is a child of the body, which doesn't have a height specified, so giving the container a percentage height causes it to have zero height. Try adding `html, body { height: 100%; }` if you want to give the container a height that is a percentage of the window height. – John Mellor Nov 07 '18 at 01:01
  • 1
    It seems like you all blindly upvote this answer because of its popularity, without assessing the question fully. OP clearly stated that he doesn't know the height of the container he's trying to make as wide as they are high, which simply *isn't* what this answer adheres to. John here uses a fixed height of 400 pixels in his JS Bin example. Remove it, and the example breaks. There's a reason this isn't the accepted answer. – Gust van de Wal Nov 27 '18 at 16:01
  • This is quite smart! I was able to properly size my input range slider in webkit even using the rotate() trick. Thanks! – chrilith Jan 26 '19 at 10:54
  • @GustvandeWal This works fine if you don't _know_ the height of the container, and even if that height is not _fixed_. The container does however need to have its height determined by _something_. For example https://jsbin.com/koyuxuh/edit shows an example where the container's height is determined by the amount of text in a sibling element. – John Mellor Jan 26 '19 at 13:12
  • @JohnMellor Still there's no way in his or your example to have a long, uninterrupted line of text inside the element, and have it stretch to its width. Quoting OP: `I have a set of elements and require that their minimum width be equal to their height, but the height is not explicitly set.` This means that if you fill this element with a single node of ratio 2:1, the element itself should stay 2:1, and not 1:4, like in your examples. – Gust van de Wal Jan 28 '19 at 15:27
  • This is fantastic solution indeed. However, if your container is being resized by flex (being a flex-item) this won't work - you'll be limited to use only img element with possibility to set some background image, which perfectly solved my task. – Mesqalito Feb 12 '19 at 16:15
  • @Matmarbon I don't believe this is possible with psuedoelements, as the browser does not scale them proportionally. See https://jsfiddle.net/kofLbq09/5/ where I try wrapping around an image or svg element (which work to create a 16:9 proportion) and te same thing with psuedoelements (which do not work) – SamGoody Oct 26 '20 at 12:55
  • Unfortunately not working on Safari in MacOS Big Sur. – Le Mot Juiced Mar 12 '21 at 14:58
  • This is a great solution, yet doesn't work on iPhones if the image is inside flexbox. – purplefeel Nov 04 '22 at 18:27
18

Now the new aspect-ratio property can easily do this and it work in both situation:

  1. Height based on width
  2. Width based on height

We simply set the ratio and either the width or the height:

.box {
  height:80vh;
  background:red;
  aspect-ratio:9/6;
}

.box1 {
  width:50vw;
  background:green;
  aspect-ratio:9/6;
}
<div class="box">

</div>

<div class="box1">

</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
2

No image, just plain HTML + CSS with a 100% variable aspect ratio. Trick is to use an inline SVG which provides the desired aspect ratio.

.width-adjusting-to-height {
  display: inline-block;
  height: 200px; /* change to whatever you like */
  background: tomato;
  position: relative;
}

.width-adjusting-to-height > svg {
  height: 100%;
}

.width-adjusting-to-height .content {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
<div class="width-adjusting-to-height">
  <!-- the viewbox will provide the desired aspect ratio -->
  <svg viewBox="0 0 50 100"></svg>
  <div class="content">content goes here</div>
</div>
Thomas
  • 2,338
  • 2
  • 23
  • 32
  • You don't need the xmlns link, do you? – Le Mot Juiced Mar 11 '21 at 21:31
  • I can't get this working when the height is set to a percentage, which is what OP asked. The height cannot be explicitly set or it's not a solution to the question. – Le Mot Juiced Mar 12 '21 at 14:18
  • This solution also does work with relative heights. Generally speaking: when using relative units, make sure the corresponding parent element has set its height properly. – Thomas Mar 12 '21 at 14:43
  • see this example with a relative height https://codepen.io/wilmaknattern/pen/LYbqxjW – Thomas Mar 12 '21 at 14:46
  • I realize this could be an incendiary question, and if you're being sincere please accept my apologies, but: are you trolling? The width doesn't scale at all in that link. Again I super apologize if you're being sincere. – Le Mot Juiced Mar 12 '21 at 14:49
  • it does. at least in Edge, Chrome and Firefox. Also in Safari it does scale initially, but does not rescale when the viewport changes, which seems to be more like a problem in Safari than in the solution. – Thomas Mar 12 '21 at 14:55
  • Oh I agree it's Safari's fault, not yours. But ignoring Safari isn't an option. Safari's problem is everybody's problem, which sucks. I've been running into this more and more frequently. That aside, this is a wonderful and simple solution, good job. – Le Mot Juiced Mar 12 '21 at 15:24
0

There is another very simple css solution but only for the exact case the OP presented in his jsfiddle:

label {
  background-color: Black;
  color: White;
  padding: 1px;
  font-family: sans-serif;
  text-align: center;
  vertical-align: middle;
  display: inline-block;
  min-width: 1em; /* set width to the glyphs height */
}
<label>A</label><br/>
<label>B</label><br/>
<label>C</label><br/>
<label>D</label><br/>
<label>E</label><br/>
<label>F</label><br/>
<label>R6a</label>
Simon
  • 7,182
  • 2
  • 26
  • 42
0

I had the same requirement and I use @John Melor solution with good success... until it did not work on Firefox. I tried reproducing my specific issue by testing the different jsbin code examples here, but no luck. Probably a highly specific browser bug based on my actual DOM. If someone has a jsbin of that issue, please share!

I tried many combinations that all fail on the same thing: the container will not expand to the image width. Firefox declare that image has the width of its height, but the parent is 1px width no matter what I tried.

For whomever find himself in that situation, I used a js bugfix that stays local to the image element. If the height of the container isn't specified like in the initial OP question, that could also be useful to you.

In JSX/ES6:

const TRANSPARENT_PIXEL = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
const setWidthToHeight = e => { e.target.style.width = e.target.height }
const force1x1Ratio = <img src={TRANSPARENT_PIXEL} onLoad={setWidthToHeight} />

With plain ol' HTML

<img 
    src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 
    onLoad="javascript:function(e) { e.target.style.width = e.target.height + 'px' }" />
Pandaiolo
  • 11,165
  • 5
  • 38
  • 70
0

Bad news for Safari users: as of the 13th of March 2021 the solutions on this page won't work, at least in Mac OS Big Sur.

For some reason something's broken in Safari when it comes to this.

There's a stopgap solution that won't be an option in all cases, but in some, it is even easier than the others here:

  • Use vh units for the width:

    .container {
        height: 40vh;
        width: 20vh;
    }
    

Suuuuuuper simple, and the width always stays in ratio with the height.

  • For more flexibility, in some cases you can even use percentages:

    .container {
       height: 40%;
       width: 20vh;
    }
    

Which will match the container 40% to its parent, and keep the width always in ratio with the height. But this only works when the height of the parent element itself is always in a consistent ratio to the size of the overall viewport.

So, not a universal fix, but in many cases could be the simplest route.

Le Mot Juiced
  • 3,761
  • 1
  • 26
  • 46
-1

From W3C Box Model on padding:

The percentage is calculated with respect to the width of the generated box's containing block, even for 'padding-top' and 'padding-bottom'. If the containing block's width depends on this element, then the resulting layout is undefined in CSS 2.1.

So you can have it using only CSS. See this example from this blog post on another subject equally interesting. The trick to creating ratio boxes is using something like

element::before {
  content: '';
  display: block;
  padding-bottom: 100%;
}
Gust van de Wal
  • 5,211
  • 1
  • 24
  • 48
Henry Mazza
  • 764
  • 1
  • 6
  • 18
  • 5
    Your answer sets _height_ based on _width_ which is the opposite of what OP asked for (and much easier). There are already good solutions for that at https://stackoverflow.com/questions/1495407/maintain-the-aspect-ratio-of-a-div-with-css – John Mellor Jan 26 '19 at 13:41
-1

If you are trying to make squares, you'll have to account for both cases (W > H and W < H):

$('label').each(function() {
    if ($(this).height() > $(this).width()) {
        $(this).css('min-width', $(this).height());
    } else {
        $(this).css('min-height', $(this).width());
    }
});
Blender
  • 289,723
  • 53
  • 439
  • 496
-1

Use flexbox and padding. Use @media queries to determine if the min-aspect-ratio of the viewport. Then adjust accordingly.

Simplified solution with a 16:9 aspect ratio.

html,body {
  height: 100%;
}

.parent {
  height: 100%;
  width: 100%;
  border-style: solid;
  border-width: 2px;
  border-color: black;
  /* to center */
  display: flex;
  align-items: center;
  justify-content: center;
}

.child {
  width: 100%;
  /* 16:9 aspect ratio = 9 / 16 = 0.5625 = padding-bottom*/
  padding-bottom: 56.25%;
  background: blue;
}

@media (min-aspect-ratio: 16/9) {
  .parent>img {
    height: 100%;
    /* make the img's width 100% of the height of .container */
  }
  .child {
    height: 100%;
    /*16:9 aspect ratio = 9 / 16 = 177.77 = width*/
    width: 177.77vh;
    padding: 0;
    margin: 0px;
    background: red;
  }
}
<div class="parent">
  <div class="child">
    content that is not images...
  </div>
</div>

Here's a fiddle.

Prosy Arceno
  • 2,616
  • 1
  • 8
  • 32
-10

I don't believe this is possible without JS, but maybe someone can prove me wrong?

CSS doesn't allow you to use dynamic variables, so if the height isn't set until the page loads, I'm not sure how this could be done.

Jeff
  • 12,147
  • 10
  • 51
  • 87