How to make div element auto-resize maintaining aspect ratio?

The aspect-ratio ref property has a good support now so you can use the below:

body {
  height: 100vh;
  margin: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background: gray;
}


.stage {
  --r: 960 / 540;

  aspect-ratio: var(--r);
  width:min(90%, min(960px, 90vh*(var(--r))));
  
  display: flex;
  justify-content: center;
  align-items: center;
  
  
  background: 
    /* this gradient is a proof that the ratio is maintained since the angle is fixed */
    linear-gradient(30deg,red 50%,transparent 50%),
    chocolate;
}
<div class="stage">
  <p>Some text</p>
</div>

Old answer

Here is an idea using viewport unit and clamp(). It’s a kind of if else to test if the width of the screen is bigger or smaller than the height (considering the ratio) and based on the result we do the calculation.

In the code below with have two variables cv and ch and only one of them will be equal to 1

  • If it’s cv then the width is bigger so we set the height to cv and the width will be based on that height so logically cv/ratio

  • If it’s ch then the height is bigger so we set the width to cv and the height will be based on that width so logically ch/ratio

In the clamp() I am using 1vh/1vw that I multiple by 90 which is equivalent to your 90% to have 90vh/90vw

body {
  height: 100vh;
  margin: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background: gray;
}


.stage {
  --r: calc(960 / 540);
  
  --cv: clamp(0px,(100vw - 100vh*var(--r))*10000,1vh);
  --ch: clamp(0px,(100vh*var(--r) - 100vw)*10000,1vw);

  height: calc((var(--cv) + var(--ch)/var(--r)) * 90 );
  width:  calc((var(--ch) + var(--cv)*var(--r)) * 90 );
  
  max-width: 960px;
  max-height: 540px; /* OR calc(960px/var(--r)) */
  
  display: flex;
  justify-content: center;
  align-items: center;
  
  
  background: 
    /* this gradient is a proof that the ratio is maintained since the angle is fixed */
    linear-gradient(30deg,red 50%,transparent 50%),
    chocolate;
}
<div class="stage">
  <p>Some text</p>
</div>

In theory the clamp can be simplified to:

--cv: clamp(0px,(1vw - 1vh*var(--r)),1vh);
--ch: clamp(0px,(1vh*var(--r) - 1vw),1vw);

But to avoid any rounding issue and to not fall into values like 0.x I consider a big value to make sure it will always be clamped to 1 if positive

UPDATE

It seems there is a bug with Firefox so here is another version of the same code:

body {
  height: 100vh;
  margin: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background: gray;
}


.stage {
  --r: calc(960 / 540);
  
  --cv: clamp(0px,(100vw - 100vh*var(--r))*100,90vh);
  --ch: clamp(0px,(100vh*var(--r) - 100vw)*100,90vw);

  height: calc((var(--cv) + var(--ch)/var(--r)) );
  width:  calc((var(--ch) + var(--cv)*var(--r)) );
  
  max-width: 960px;
  max-height: 540px; /* OR calc(960px/var(--r)) */
  
  display: flex;
  justify-content: center;
  align-items: center;
  
  
  background: 
    /* this gradient is a proof that the ratio is maintained since the angle is fixed */
    linear-gradient(30deg,red 50%,transparent 50%),
    chocolate;
}
<div class="stage">
  <p>Some text</p>
</div>

Leave a Comment