Blog

Responsive background images in Umbraco with focal point preservation

Responsive background images in Umbraco with focal point preservation

On a previous post, we demonstrated how you can use the SRCSET attribute together with some code to automatically create responsive images for Umbraco. That approach is good for images used inside your page, but unfortunately SRCSET cannot be used with background images, which leaves us with the problem on choosing between using a huge image for all resolutions or writing some custom CSS media queries for each image to adapt it to various viewport widths in order to make our site faster and preserve bandwidth. This would also mean we would have to have a set of resized images for each media query. This would require hard work for every single image and would not include a bit of automation.

Moreover, there are plenty of cases where you have background images defined in your original CSS but need them to be defined from within Umbraco. "Oh, this is simple", you'll say. "I'll just replace the part where the background image is hardcoded in CSS with an inline STYLE attribute that'll be taking its value from an Umbraco property and Bob's your uncle". This is true and necessary to do when you need to control/change your background image from the Umbraco back-end but still leaves you with the following problem(s):

  • In order to have different sizes for different resolutions you either have to have an image cropper with named crops in place so you can use each named crop in your respective media query, OR
  • You have to have custom resized images for each resolution that you will use in your media queries based on file naming conventions or something similar but this will involve out-of-the-backend handling, OR
  • You create your media queries inline in your template/partial view using the GetCropUrl() extension method for each different size so you get all image sizes you need. 

In all of the above cases, you will still have to manually define your media queries as well as the set of different images needed. And you will also have an additional problem (unless you - carefully - use named crops): If the width/height ratio of your background image changes for different viewport sizes, you may find that your focal point is not enough, since different ratios will make different parts of the image visible, often hiding the main object of focus.

But what if there was a better way?

Well, there is. It may seem complicated at first, but it really isn't.

By using the following extension method together with a custom class, we can automatically create the different image sizes needed AND respect the focal point even if the visible part of the background image changes width/height ratio between viewports.

Let's start with the usage: 

 

Now let's explain what it does:

(I'm using a dummy strongly-typed model with a property alias called "Image" for the example, but you can use this code with non-strongly-typed models as well)

First, we get an ImageData object instance via the GetImageData() extension method (this is a custom class created by us, the code will be listed below) from the Image property. This essentially contains some information about the image that we are going to use.  

Next, we pick a spot to emit the inline CSS media queries that are automatically created by the ImageData.GetBreakPointCss() method. This method has two optional parameters:

  • The viewWidth optional parameter is there just for you to see and defines what percentage of the horizontal area the image will cover, so if you have two background images side by side you should set each to 50 and so on. For now, we suppose we have only one background image that covers the full page width.
  • The breakPoints optional parameter is a string containing pairs of horizontal resolutions. The first one is the min-width for our viewport and the second one is the size the image will be resized via the GetCropUrl() extension method. A value of 320:480 means that for a min-width of 320px, the image will be resized to a 480px width. There are default values in place but you can use your own pairs if you need more or less distinct viewport widths. 

Last, we add a class to the element that will contain the background image. In our example this is a <section> tag. The style we add is in the form media-image-xxxxxxx where xxxxxx is the value of the imageData.StyleUid property, a unique id for each media query generated by the code. We also add a STYLE attribute that fixes the background position of the image (essentially "moves" the image) in a way relative to where the original focal point has been set so that even when visible image height/width ratios change between resolutions we will always be sure that the visible part will include what's under the focal point.

Please note that in the code of GetImageData() we check if the focal point is null. This could happen if we upload an image and not save it and/or mass import images, and in this case we manually set the focal point to the center of the image. 

 

Here's the code for the extension method:

 

Here's the code for the ImageData class:

 

Let's see a sample output: 

<style>
@media screen and (min-width: 320px){.media-image-be8c28a9-b9eb-470b-aee8-209f70a6696e {
	background-image:url('/media/1003/sampleimage.jpg?anchor=center&mode=crop&width=480&upscale=false&rnd=131584267630000000'); }}
@media screen and (min-width: 480px){.media-image-be8c28a9-b9eb-470b-aee8-209f70a6696e {
	background-image:url('/media/1003/sampleimage.jpg?anchor=center&mode=crop&width=768&upscale=false&rnd=131584267630000000'); }}
@media screen and (min-width: 768px){.media-image-be8c28a9-b9eb-470b-aee8-209f70a6696e {
	background-image:url('/media/1003/sampleimage.jpg?anchor=center&mode=crop&width=960&upscale=false&rnd=131584267630000000'); }}
@media screen and (min-width: 960px){.media-image-be8c28a9-b9eb-470b-aee8-209f70a6696e {
	background-image:url('/media/1003/sampleimage.jpg?anchor=center&mode=crop&width=1280&upscale=false&rnd=131584267630000000'); }}
@media screen and (min-width: 1280px){.media-image-be8c28a9-b9eb-470b-aee8-209f70a6696e {
	background-image:url('/media/1003/sampleimage.jpg?anchor=center&mode=crop&width=1366&upscale=false&rnd=131584267630000000'); }}
@media screen and (min-width: 1366px){.media-image-be8c28a9-b9eb-470b-aee8-209f70a6696e {
	background-image:url('/media/1003/sampleimage.jpg?anchor=center&mode=crop&width=1600&upscale=false&rnd=131584267630000000'); }}
@media screen and (min-width: 1600px){.media-image-be8c28a9-b9eb-470b-aee8-209f70a6696e {
	background-image:url('/media/1003/sampleimage.jpg?anchor=center&mode=crop&width=1920&upscale=false&rnd=131584267630000000'); }}
</style>
<section class="section section--testimonial media-image-be8c28a9-b9eb-470b-aee8-209f70a6696e" style="background-position:50.0%  50.0%">
    <!-- your content here -->
</section>

 

This may look like it's bloating your markup a bit. But consider the bandwidth (and speed increase) you will enjoy from not having to load those huge 1920px width background images in mobile devices ever again.

Happy coding!

 

 



;