Responsive images with srcset and image cropper

Responsive images with srcset and image cropper

One of the greatest challenges that come with creating a responsive, mobile-friendly site is to adapt your images for various smaller resolutions so that they're not the bandwidth-eating monsters you normally get on a desktop resolution. Who wants a slider with ten 1920x600px banners on a 480px wide screen, especially when your mobile device is not connected to Wi-Fi and is consuming data from your mobile data package? 

The problem is that estimating the viewport size on the server side is not always accurate and can sometimes lead to wrong image dimensions, damaging the overall layout of your site. You can, of course, use CSS with media queries to define different images for different viewports on the client side, but my guess is that if you're reading this you already have tried or you don't want to. And you're right. It's a very boring and "static" process, and to make it dynamic like the rest of the Umbraco stuff you'll have to use tons of inline CSS - not very good for performance.

The SRCSET attribute

Enter srcset - a HTML 5 attribute that delegates image selection to the browser. You just hint what image goes with what viewport size and let the browser decide what to load.

If you haven't heard about srcset yet, here's an excellent article that explains it better than I ever could:

Umbraco already features the Image Cropper data type which uses Image Processor ( behind the scenes, so Umbraco can take an image a user uploads and resize it or crop it, in real time, to given dimensions (and then cache it so that it doesn't have to do that again)

Using SRCSET together with the Image Cropper

So, what can we do if we combine srcset with Image Cropper? We can have a function that automatically generates the srcset attribute data for a given image. See this example:

<img srcset="/media/1006/city.jpg?anchor=center&mode=crop&width=94&height=52&rnd=131471984450000000 94w,
/media/1006/city.jpg?anchor=center&mode=crop&width=300&height=168&rnd=131471984450000000 300w,
/media/1006/city.jpg?anchor=center&mode=crop&width=600&height=337&rnd=131471984450000000 600w,
/media/1006/city.jpg?anchor=center&mode=crop&width=768&height=432&rnd=131471984450000000 768w,
/media/1006/city.jpg?anchor=center&mode=crop&width=1024&height=576&rnd=131471984450000000 1024w,
/media/1006/city.jpg?anchor=center&mode=crop&width=1280&height=720&rnd=131471984450000000 1280w"

This is an IMG tag with srcset attribute and a fallback src attribute at the end, while all image versions have been programmatically created with Image Cropper based on a single Media image (/media/1006/city.jpg). Browsers which support srcset will select the best image to download, for example, if the viewport is 600px or less it'll load the 600px width version of the image.

The srcset attribute is created automatically by a call like the following:

srcset="@myNode.Image.GetSrcSet(1280, 720, "94,300,600,768,1024,1280")"
src="@myNode.Image.GetCropUrl(width: 1280, height: 720, imageCropMode: ImageCropMode.Max, upScale: true)"

where "myNode" is an IPublishedContent object and "Image" is a strongly-typed Image Cropper property.

The GetSrcSet extension method

The GetSrcSet extension method does all the work of calculating stuff and providing the srcset data. The GetSrcSet accepts three optional arguments: width and height of original crop, and a comma delimited string of max viewport widths

The height and width arguments are used if you need to actually crop the image (and, subsequently, change its original aspect ratio). GetSrcSet will calculate all other images' crops based on the new aspect ratio. Or, you can just not define width and height at all to go with the original image's aspect ratio (without cropping anything). Another way is to only define the width attribute, which will serve as the max width for the image, in case the image uploaded is huge.

Also, the comma delimited list of viewport widths can also be omitted since there's a default value in place. You can use it when you need different specific values, though.

The "src" argument is provided via the classic GetCropUrl extension method and serves as a fallback in case the browser doesn't support the srcset attribute. It's good practice to have the maximum expected image size there, just in case. The browser will NOT load this image at all if it supports srcset.

Here's the code for the extension method. Current namespace is DotSee.Common but you can change it to whatever suits you.


Some additional notes:

Browser compatibility - If you're worried that srcset is not supported by all browsers, you're - partially - right. Older browser versions do not support it. But they'll always fall back to the original src attribute, so worst case scenario is that they'll be loading huge images in mobile resolutions - which is incentive enough for users to upgrade :) Here's a compatibility chart:

Verifying that the browser is choosing the correct image - Please note that the browser will prefer images that are in your computer's local cache to those it has to get from the Internet. So, if you already have loaded a page containing some images once, and then switch your browser to a smaller resolution, it'll still load the larger images. You can avoid that in Chrome by enabling Chrome's Debug mode, which has an option to disable caching. (And probably, with a similar method, in other browsers too).

Not suitable for background images - Srcset works only on IMG tags and not on CSS background images. To have a similar solution with background images you either have to involve Javascript (this guy has all the information on the subject) or use a hybrid inline css with media queries generation solution (that can also take advantage of Image Cropper in Umbraco). More on this on a next post.

Have fun!