Recently, I've been trying to create a centered div
with an aspect ratio of 3 / 7 that fills its parent the best can, acting something like object-fit: contain
. Unfortunately, object-fit: contain
doesn't seem to work for non-replaced elements.
Here's a visualization of what I want to achieve in Flutter, which is something I have slightly more experience in.
https://dartpad.dev/?id=eec1210050a099df1c5422fa66e891e5
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: AspectRatio(
aspectRatio: 3 / 7,
child: ColoredBox(color: const Color(0xFF42A5F5))),
),
),
);
}
}
When when the height of the container is less than its width, the box's height should be the same as its container.
When when the width of the container is less than its height, the box's width should be the same as its container.
All the while, it should always stay in the center of the screen and maintain its 7 / 3 aspect ratio.
This is what I've come up with so far, but it doesn't react to the height becoming less than the width.
* {
margin: 0;
}
html,
body {
width: 100%;
height: 100%;
}
.container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
flex-direction: column;
}
.blue {
background-color: royalblue;
aspect-ratio: 3 / 7;
flex-grow: 1;
max-width: 100%;
}
<div class="container">
<div class="blue">
</div>
</div>
On Discord, just_13eck suggested a solution that had this CSS instead:
body {
margin: 0;
}
.container {
display: flex;
block-size: 100vh;
}
.blue {
flex: 1 1 0;
background-color: royalblue;
aspect-ratio: 3 / 7;
margin-inline: auto;
margin-block: auto;
max-inline-size: calc((3 / 7) * 100vh);
max-block-size: 100%;
}
And it works! BUT it only works relative to the viewport size.
Could anybody help me? I'd really appreciate it; Thanks!
NEW ANSWER
Thanks to @C3Roe for the container query idea!
* {
margin: 0;
}
html,
body {
width: 100%;
height: 100%;
}
.containment-area {
width: 100%;
height: 100%;
container: containment / size;
}
.container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
@container containment (aspect-ratio < 3 / 7) {
.container {
flex-direction: row;
}
}
.non-replaced-element {
background-color: royalblue;
aspect-ratio: 3 / 7;
flex-grow: 1;
}
<div class="containment-area">
<div class="container">
<div class="non-replaced-element">
</div>
</div>
</div>
With help from this StackOverflow answer.
OLD ANSWER
"You're not going to be able to remove the viewport unit dependencies. By default, block-level elements take up only as much vertical room as the content requires. In this case, there is no content so the elements shrink to literally nothingness. If you want there to be content-less height you use vh
. Or even if you want there to be contentful height, you use vh
to set that initial full-height containing block." - just_13eck
If you're willing to use JS though, you can use this solution:
function resizeInner() {
const container = document.querySelector('.container');
const inner = document.querySelector('.inner');
const aspectRatio = 3 / 7; // width / height
// Get the computed style to accurately get the container's dimensions
const containerStyle = window.getComputedStyle(container);
const containerWidth = parseFloat(containerStyle.width);
const containerHeight = parseFloat(containerStyle.height);
let innerWidth, innerHeight;
if (containerWidth / containerHeight > aspectRatio) {
// Container is wider than the target aspect ratio
innerHeight = containerHeight;
innerWidth = innerHeight * aspectRatio;
} else {
// Container is taller than the target aspect ratio
innerWidth = containerWidth;
innerHeight = innerWidth / aspectRatio;
}
inner.style.width = innerWidth + 'px';
inner.style.height = innerHeight +'px';
}
// Initial resize
resizeInner();
// Resize on window resize
window.addEventListener('resize', resizeInner);
// If the container size might change due to other factors, you can use a ResizeObserver
const resizeObserver = new ResizeObserver(resizeInner);
resizeObserver.observe(document.querySelector('.container'));
* {
margin: 0;
}
html, body {
width: 100%;
height: 100%;
}
.container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: #f0f0f0;
}
.inner {
background-color: #3498db;
}
<div class="container">
<div class="inner">
</div>
</div>