I stumbled upon what I consider a smart solution for this problem, using <svg>
and display:grid
.
A display:grid
element allows you to occupy the same space with two (or more) of its children, using the same grid-area
.
This means means they are all flow content, overlapped, and out of the box the taller one sets the ratio.
One of them will be an <svg>
in charge of setting the ratio. The other, actual content. If actual content is short and never fills up the entire ratio (and you just want it centered in a space with this ratio), simply center it (see first runnable snippet below).
<div class="ratio">
<svg viewBox="0 0 1 1"></svg>
<div>
I'm square
</div>
</div>
.ratio {
display: grid;
}
.ratio > * {
grid-area: 1/1;
}
Set <svg>
s ratio to whatever you want:
<svg viewBox="0 0 4 3"></svg>
<svg viewBox="0 0 16 9"></svg>
.ratio {
display: grid;
}
.ratio > * {
grid-area: 1/1;
}
/* below code NOT needed for setting the ratio
* I just wanted to mark it visually
* and center contents
*/
.ratio div {
border: 1px solid red;
display: flex;
align-items: center;
justify-content: center;
}
_x000D_
<div class="ratio">
<svg viewBox="0 0 7 1"></svg>
<div>
Fixed ratio 7:1
</div>
</div>
_x000D_
If you need a solution where the content element has more content you want confined into a scrollable area with desired ratio, set position:relative
on the parent and position:absolute; height:100%; overflow-y: auto;
on the content, allowing the flow content element (the <svg>
) to set the size, therefore the ratio.
.ratio {
display: grid;
position: relative;
}
.ratio > * {
grid-area: 1/1;
}
.ratio > div {
height: 100%;
overflow-y: auto;
position: absolute;
/* the rest is not needed */
border: 1px solid red;
padding: 0 1rem;
}
_x000D_
<div class="ratio">
<svg viewBox="0 0 7 2"></svg>
<div>
<h1>Fixed ratio 7:2</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. A scelerisque purus semper eget. Sem nulla pharetra diam sit amet nisl suscipit adipiscing bibendum. A cras semper auctor neque vitae tempus quam pellentesque nec. Morbi enim nunc faucibus a pellentesque sit amet porttitor. Arcu odio ut sem nulla. Sed viverra ipsum nunc aliquet bibendum enim facilisis gravida neque. Cras tincidunt lobortis feugiat vivamus at augue eget. Laoreet sit amet cursus sit amet. Amet nulla facilisi morbi tempus iaculis urna id volutpat. Leo in vitae turpis massa sed elementum tempus egestas sed. Egestas integer eget aliquet nibh. Dolor sit amet consectetur adipiscing elit.
<p>Ut aliquam purus sit amet. Eget magna fermentum iaculis eu non diam phasellus vestibulum. Diam in arcu cursus euismod quis viverra nibh. Nullam vehicula ipsum a arcu cursus vitae congue. Vel orci porta non pulvinar neque laoreet suspendisse. At tellus at urna condimentum mattis pellentesque. Tristique senectus et netus et malesuada. Vel pretium lectus quam id leo in. Interdum velit euismod in pellentesque. Velit euismod in pellentesque massa placerat duis. Vitae suscipit tellus mauris a diam maecenas sed enim.
<p>Mauris a diam maecenas sed enim ut sem. In hendrerit gravida rutrum quisque. Amet dictum sit amet justo donec enim diam. Diam vulputate ut pharetra sit amet aliquam id. Urna porttitor rhoncus dolor purus non enim praesent. Purus in massa tempor nec feugiat nisl pretium. Sagittis vitae et leo duis ut. Facilisi nullam vehicula ipsum a arcu cursus vitae congue mauris. Volutpat odio facilisis mauris sit amet massa vitae tortor condimentum. Aliquam purus sit amet luctus venenatis lectus magna. Sit amet purus gravida quis blandit turpis. Enim eu turpis egestas pretium aenean. Consequat mauris nunc congue nisi. Nunc sed id semper risus in hendrerit gravida rutrum. Ante metus dictum at tempor. Blandit massa enim nec dui nunc mattis enim ut.
</div>
</div>
_x000D_
As @emjay noted in a comment below, the ratio svg can be placed in one of the parent's pseudo-elements, as long as it's properly encoded:
.three-squares {
display: grid;
border: 1px solid red;
}
.three-squares > *, .three-squares:before {
grid-area: 1/1;
}
.three-squares:before {
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 1'%3E%3C/svg%3E");
line-height: 0;
}
_x000D_
<div class="three-squares">
<div>I'm 3:1</div>
</div>
_x000D_
When used inside a pseudo-element, the <svg>
becomes a replaced element which, by default, sits on a baseline of variable height (4px
in Chrome, 3.5px
in Firefox). The height of the baseline varies according to line-height
, which is why we need to set line-height: 0
on the pseudo to get an accurate ratio. More details here.
I personally prefer the version where the <svg>
is placed in markup, as I can have a single class (.ratio
) dealing with containers of various ratios (as opposed to having a class for each individual ratio I might need).