As far as I know, you have one possibility.
Note: Consider this a hack which probably should not be used in production code.
Percentage values of the margin and padding properties are relative to the width of the containing block. This means margin-top and padding-top (running along the Y axis) will respond to changes made to the width of the parent (running along the X axis).
In the following example, .child has a width of 50%, which is relative to the width of the parent. To have its height respond to the width of the parent, we can simulate a height using the padding-top property. Now both the width and the simuated height of .child are relative to the width of .parent, thus preserving the aspect ratio.
(Try resizing .parent to see how .child responds while keeping the aspect ratio.)
.parent {
width: 200px;
height: 150px;
background: orange;
overflow: scroll;
resize: both;
border: 1px solid #999;
}
.child {
margin: auto;
width: 50%;
height: 0;
padding-top: calc(50% * 0.75);
background: yellow;
}
<div class="parent">
<div class="child"></div>
</div>
Now, as anyone will quickly find out, this does not allow for any contents inside .child. To fix this, you can introduce another nested <div> named .grand-child. Make .child relatively positioned and .grand-child absolutely positioned inside of .child.
.parent {
width: 200px;
height: 150px;
background: orange;
overflow: scroll;
resize: both;
border: 1px solid #999;
}
.child {
position: relative;
margin: auto;
width: 50%;
height: 0;
padding-top: calc(50% * 0.75);
background: yellow;
}
.grand-child {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: red;
}
<div class="parent">
<div class="child">
<div class="grand-child">
Hello world
</div>
</div>
</div>