Creating fluid images when they stand alone in a layout is easy enough nowadays. However, with more sophisticated interfaces we often have to place images inside responsive elements, like this card:
For now, let’s say this image is not semantic content, but only decoration. That’s a good use for background-image. And because in this context the image contains an object, we can’t allow any parts to be cropped out when it’s responsive, so we’d pick background-size: contain
.
Here’s where it starts to get tricky: on mobile devices, this card shifts direction and becomes vertical, with the image on top. We can make that happen with any sort of CSS layout technique, and probably best handled with CSS grid or flexbox.
But as we test for smaller screens, because of the contain
property, this is what we get:
That’s not very nice. The image resizes to maintain its aspect ratio without cutting off any details, and if the image is important content and should not be cropped, we can’t change background-size
to cover
.
At this point, our next attempt might be familiar to you: placing the image inline, instead the background.
On desktop, this works fine:
It’s not bad on mobile either:
But on smaller screens, because of all the fixed sizes, the image’s proportions get distorted.
We could spend hours fiddling with the image, the card, the flex properties, going back and forth. Or, we could…
Separate main content from the background
This is the base for obtaining much more flexibility and resilience when it comes to responsive images. It might not be possible 100% of the time but, in many cases, it can be achieved with a little effort on the design side of things, especially if this approach is planned beforehand.
For our next iteration, we’re placing our strawberries image on a transparent background and setting what was the blue color in the raster image with CSS instead. Go ahead and play with viewport sizes in this demo by adjusting the size of the sample space!
Looking deeper at the styles, notice that we’ve also added padding to the div that holds the image, so the strawberries don’t come too close to the edges. We have full control of how close or distant we want them to be, through this padding.
Note how we’re also using negative margins to compensate for the padding on our outer card wrapper, otherwise we’d get white space all around the image.
object-fit
property for inline images
Use the As much as the previous demo works, we can still improve the approach. Up to now, we’ve assumed that the image was un-semantical content — but with this layout, it’s also likely that the image illustration could be more than decoration.
If that’s the case, we definitely don’t want the image to get cut off because that would essentially amount to data loss. It’s semantically better to put the image inline instead of a background to prevent that, and we can use the object-fit
property to make it happen.
We’ve extracted the strawberries from the background and it’s now an inline <img>
element, but we kept the background color in that same image div.
Finally, combining the object-fit: contain
with a 100% width makes it possible to resize the window and keep the aspect ratio of the strawberries. The caveat of this approach, however, is that we need to set a fixed height for the image on the desktop version — otherwise it’s going to follow the proportion of the set width (and reducing it will alter the layout). That might make things too constrained if we need to generate these cards with a variable amount of text that breaks into several lines.
aspect-ratio
Coming soon: The solution for the concern above might be just around the corner with the upcoming aspect-ratio
property. This will enable setting a fixed ratio for an element, like this:
.el {
aspect-ratio: 16 / 9;
}
This means we’ll be able to eliminate fixed height and replace it with our calculated aspect ratio. For example, the dimensions in the desktop breakpoint of our last example looked like this:
.image {
/* ... */
height: 184px;
width: 318px;
}
With aspect-ratio
, we could remove the height declaration and do the math to get the closest ratio that amounts to 184:
.image {
/* ... */
width: 318px; /* Base width */
height: unset; /* Resets the height that was set outside the media query */
aspect-ratio: 159 / 92; /* Amounts close to a 184px height */
}
The upcoming property is better explored in this article, if you want to learn more about it.
In the end, there are multiple ways to achieve reliably responsive images in a variable proportion layout. However, the trick to make this job easier — and better — does not necessarily lie with CSS; it can be as simple as adapting your images, whether that’s by separating the foreground from background (like we did) or selecting specific images that will still work if a fair portion of the edges get cropped.
You can do the ratio using: :before with top padding (in %) on the image container and image then with position absolute and object-fit OR as a background and size: cover.
Thank you for a thought provoking article.
If I am understanding the article, in “mobile” presentation, you do not want the image to change proportion to what would be shown in larger presentation, only have the subject become larger and remain centered. You also do not want to have any odd spacing between the image and content should the image remain proportional.
For mobile presentation, this can be accomplished by placing the image within a container such that the image is scaled up and its container is set to have overflow: hidden. The caveats with either technique depend on how well centered the original image is and the degree to which it is scaled, independent of whether a fixed height is specified. Of course, if the image is not well centered, it can be translated to where it looks centered.
Please see: https://codepen.io/jlevin/pen/qBbxXjO
I am confused as to why it was necessary to achieve the effect by modifying the strawberry image by removing its background.
You can currently achieve
aspect-ratio
with adisplay:grid
wrapper and a bare bones<svg>
.See this SO answer: https://stackoverflow.com/a/53245657/1891677
Since css aspect-ratio isn’t available yet (as noted in the article), I have been using padding-top trick to control the aspect ratio of the image.
It isn’t ideal, but it does make a lot of thing easier. Especially in card, as I could have one picture to be rendered onto multiple size/dimension of card with ease (with object-fit: cover; instead of contain)
See the code: https://jsfiddle.net/r2sw78c4/
Thanks! It was helpful
Hi, thank you for this wonderful article, one question I’m asking myself is :
what happens if we are provided with images of several sizes ? With the image-part-of-the-background-approach, I can change the image size, but with the inline-approach, I don’t see how this can be done?