Responsive Sizing Without Using Absolute Positioning
Responsive Sizing Without Using Absolute Positioning
Background
Often times you want to size some DOM element based on an aspect ratio. This is useful for images and video, where you want the media to fill the available space while maintaining the original aspect ratio. The classic solution to this is outlined here: https://css-tricks.com/aspect-ratio-boxes, and is sometimes referred to as the padding bottom (or top) trick.
One drawback of this approach is that it requires you to place your content within a position: absolute
container. This means that anything inside that may have position: absolute
is now positioned relative to the container instead of what it otherwise would. The following contrived example shows where this limitation could cause problems:
In this case, you could move the orange box outside of the scrolling area, but there may be cases where your DOM structure does not allow you to do this.
tl;dr
See the CSS and markup to achieve the layout.
Sizer With position: absolute
First, lets take a look at the classic padding trick. Essentially, we have some element (a real or pseudo element) using padding-top
to make our container the desired size. We then use position: absolute
to make our content area match the size of the container.
.container {
position: relative;
overflow: hidden;
}
.content-fill {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
<div class="container" style="width: 300px;">
<div class="sizer" style="padding-top: 56.25%"></div>
<div class="content-fill" style="background-color: #26e;">…</div>
</div>
Sizer With display: table
Where before we used position: absolute
to match sizes, this approach uses table layout's behavior. An element with display: table
will divide the available space for all columns evenly. Keeping the same structure as before, lets instead use the table display and see what we get:
.container {
display: table;
overflow: hidden;
}
.container > .content-fill,
.container > .sizer {
display: table-cell;
}
Note that our content area takes up half the desired space. While our sizer is giving our container the correct height, we are still sharing the overall width with the sizer element. We need to now make sure that we give all the available space to the content. Two approaches are:
- Give the container
table-layout: fixed
and the sizerwidth: 0
- Give the content
width: 200%
Both work, but I have gone for the former approach as it is a bit more clear.
.container {
…
table-layout: fixed;
}
.container > .sizer {
width: 0;
}
Now we have something that looks like it is the correct size:
While it looks good so far, we are not quite done yet. The content area may contain content that causes overflow. If we simply put the content inside at this point, it will cause our table-cell
's height to grow. We need to make sure that our content is limited to the size of the cell. Since cells always grow their height to fit the content, we need to create an inner container that is limited by the size of the cell:
.container > .content-fill {
height: 1px;
}
.container .content-fill-inner {
height: 100%;
}
<div class="container" style="width: 300px;">
<div class="sizer" style="padding-top: 56.25%"></div>
<div class="content-fill">
<div class="content-fill-inner" style="background-color: #26e;">
…
</div>
</div>
</div>
Note that we gave the table-cell
an explicit height (the value is not important). This is needed so that our height: 100%
for the inner container is used.
Now we have a container, we can put some content inside that will respect our aspect ratio. We can also use position: absolute
to position to our original container rather than the scrolling area. For example:
<div class="container" style="width: 300px; position: relative;">
<div class="sizer" style="padding-top: 56.25%"></div>
<div class="content-fill">
<div class="content-fill-inner" style="background-color: #26e; overflow-y: auto; …" >
…
<div style="position: absolute; bottom: 6px; right: 6px; …"></div>
</div>
</div>
</div>
The Complete Code
.container {
display: table;
table-layout: fixed;
overflow: hidden;
}
.container > .content-fill,
.container > .sizer {
display: table-cell;
}
.container > .sizer {
width: 0;
}
.container > .content-fill {
height: 1px;
}
.container .content-fill-inner {
height: 100%;
}
<div class="container">
<div class="sizer" style="padding-top: 56.25%"></div>
<div class="content-fill-sizer">
<div class="content-fill-inner">
<!-- Content goes here -->
</div>
</div>
</div>