Setting Element Width Based on Height Via CSS
Asked Answered
P

10

37

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/

Plasmagel answered 27/5, 2011 at 5:0 Comment(3)
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.Riddle
@Plasmagel Could you change the accepted answer by any chance?Gressorial
NEW CORRECT SIMPLE ANSWER IS aspect-ratio widthnumber / heightnumber;. It works in both ways. For a demo see https://mcmap.net/q/37303/-setting-element-width-based-on-height-via-cssUrinary
C
-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.

Cedilla answered 27/5, 2011 at 5:5 Comment(1)
See answer of @Josh below. It is possible.Otten
R
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.

Riddle answered 16/2, 2013 at 15:34 Comment(13)
this is a fantastic solution.. this should be the answer! Thanks @JohnDenny
Can anybody get this working via e.g. .container:first-child::before { ... }? Would probably be very helpful to have a solution using only css!Renaissance
This method can be made a bit easier to use with a <canvas>, 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.Moriarty
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 workPackhorse
@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.Riddle
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.Choanocyte
This is quite smart! I was able to properly size my input range slider in webkit even using the rotate() trick. Thanks!Roundish
@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 jsbin.com/koyuxuh/edit shows an example where the container's height is determined by the amount of text in a sibling element.Riddle
@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.Choanocyte
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.Mag
@Matmarbon I don't believe this is possible with psuedoelements, as the browser does not scale them proportionally. See 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)Despotic
Unfortunately not working on Safari in MacOS Big Sur.Calvados
This is a great solution, yet doesn't work on iPhones if the image is inside flexbox.Westbrook
E
19

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>
Executor answered 21/6, 2021 at 19:36 Comment(1)
Sadly not yet working on IOS (14...) but hopefully coming on v15(?).Chinkiang
D
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>
Dyl answered 17/1, 2021 at 2:34 Comment(7)
You don't need the xmlns link, do you?Calvados
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.Calvados
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.Dyl
see this example with a relative height codepen.io/wilmaknattern/pen/LYbqxjWDyl
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.Calvados
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.Dyl
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.Calvados
T
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>
Torhert answered 11/6, 2015 at 11:14 Comment(0)
N
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' }" />
Nondisjunction answered 28/3, 2019 at 20:14 Comment(0)
C
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.

Calvados answered 13/3, 2021 at 15:45 Comment(0)
B
-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());
    }
});
Backbend answered 27/5, 2011 at 5:17 Comment(1)
Nope, I am happy for the element to be wider than it is high, just not higher than it is wide.Plasmagel
C
-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%;
}
Conrad answered 31/5, 2013 at 20:29 Comment(1)
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 #1495907Riddle
H
-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.

Hydrolytic answered 16/5, 2021 at 18:23 Comment(0)
C
-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.

Cedilla answered 27/5, 2011 at 5:5 Comment(1)
See answer of @Josh below. It is possible.Otten

© 2022 - 2024 — McMap. All rights reserved.