<img loading=\"auto\" \/><\/code>: Let the browser make its own assessment.<\/li>\n<\/ul>\nBrowsers without this support would be able to load the image as normal thanks to the resilient nature of HTML and browsers ignoring HTML attributes that they don\u2019t understand. <\/p>\n
But… sound the loud caution klaxon! This feature has yet to land in Chrome, and there is also uncertainty about if and when other browsers might choose to implement it. We can use feature detection to decide which method we use, but this still doesn\u2019t give a solid progressive enhancement approach where the images have no dependency on JavaScript.<\/p>\n
<img data-src=\"lighthouse.jpg\" alt=\"A snazzy lighthouse\" loading=\"lazy\" class=\"lazy\" \/><\/code><\/pre>\n\/\/ If the browser supports lazy loading, we can safely assign the src\r\n\/\/ attributes without instantly triggering an eager image load.\r\nif (\"loading\" in HTMLImageElement.prototype) {\r\n const lazyImages = document.querySelectorAll(\"img.lazy\");\r\n lazyImages.forEach(img => {\r\n img.src = img.dataset.src;\r\n });\r\n}\r\nelse {\r\n \/\/ Use our own lazyLoading with Intersection Observers and all that jazz\r\n}<\/code><\/pre>\nAs a companion to responsive images<\/h3>\n Assuming that we are comfortable with the fact that JavaScript is a dependency for the time being, let\u2019s turn our attention to a related topic: responsive images.<\/p>\n
If we\u2019re going through the trouble of delivering images into the browser only when needed, it seems fair that we might also want to make sure that we are also delivering them in the best size for how they\u2019ll be displayed. For example, there’s no need to download the 1200px-wide version of an image if the device displaying it will only give it a width of 400px. Let’s optimize!<\/p>\n
HTML gives us a couple of ways to implement responsive images which associate different image sources to different viewport conditions. I like to use the picture<\/code> element like this:<\/p>\n<picture>\r\n <source srcset=\"massive-lighthouse.jpg\" media=\"(min-width: 1200px)\">\r\n <source srcset=\"medium-lighthouse.jpg\" media=\"(min-width: 700px)\">\r\n <source srcset=\"small-lighthouse.jpg\" media=\"(min-width: 300px)\">\r\n <img src=\"regular-lighthouse.jpg\" alt=\"snazzy lighthouse\" \/>\r\n<\/picture><\/code><\/pre>\nYou\u2019ll notice that each source element has a srcset<\/code> attribute which specifies an image URL<\/abbr>, and a media<\/code> attribute that defines the conditions under which this source should be used. The browser selects the most suitable source from the list according to the media conditions with a standard img<\/code> element acting as a default\/fallback.<\/p>\nCan we combine these two approaches to make lazy-loading responsive images?<\/p>\n
Of course, we can! Let\u2019s do it.<\/p>\n
Instead of having an empty image until we do our lazy load, I like to load a placeholder image that has a tiny file size. This does incur the overhead of making more HTTP<\/abbr> requests, but it also gives a nice effect of hinting at the image before it arrives. You might have seen this effect on Medium or as a result of a site using Gatsby\u2019s lazy loading mechanics.<\/p>\nWe can achieve that by initially defining the image sources in our picture element as tiny versions of the same asset and then using CSS to scale them to the same size as their higher-resolution brothers and sisters. Then, through our intersection observer, we can update each of the specified sources to point at the correct image sources.<\/p>\n
So, initially our picture element might look like this:<\/p>\n
<picture class=\"lazy\">\r\n <source srcset=\"tiny-lighthouse.jpg\" media=\"(min-width: 1200px)\">\r\n <source srcset=\"tiny-lighthouse.jpg\" media=\"(min-width: 700px)\">\r\n <source srcset=\"tiny-lighthouse.jpg\" media=\"(min-width: 300px)\">\r\n <img src=\"tiny-lighthouse.jpg\" alt=\"snazzy cake\" \/>\r\n<\/picture><\/code><\/pre>\nNo matter what viewport size is applied, we\u2019ll display a tiny 20px image. We’re going to blow it up with CSS next.<\/p>\n
Previewing the image with style<\/h4>\n The browser can scale up the tiny preview image for us with CSS so that it fits the entire picture element rather than a mere 20px of it. Things are going to get a little… pixelated, as you may imagine when a low-resolution image is blown up to larger dimensions.<\/p>\n
picture {\r\n width: 100%; \/* stretch to fit its containing element *\/\r\n overflow: hidden;\r\n}\r\n\r\npicture img {\r\n width: 100%; \/* stretch to fill the picture element *\/\r\n}<\/code><\/pre>\n <\/figure>\nFor good measure, we can soften that pixelation introduced by scaling up the image by using a blur filter.<\/p>\n
picture.lazy img {\r\n filter: blur(20px);\r\n}<\/code><\/pre>\n <\/figure>\nSwitching sources with JavaScript<\/h4>\n With a little adaptation, we can use the same technique as before to set the correct URL<\/abbr>s for our srcset<\/code> and src<\/code> attributes. <\/p>\nfunction lazyLoad(elements) {\r\n\r\n elements.forEach(picture => {\r\n if (picture.intersectionRatio > 0) {\r\n\r\n \/\/ gather all the image and source elements in this picture\r\n var sources = picture.children;\r\n\r\n for (var s = 0; s < sources.length; s++) {\r\n var source = sources[s];\r\n\r\n \/\/ set a new srcset on the source elements \r\n if (sources.hasAttribute(\"srcset\")) {\r\n source.setAttribute(\"srcset\", ONE_OF_OUR_BIGGER_IMAGES);\r\n }\r\n \/\/ or a new src on the img element\r\n else {\r\n source.setAttribute(\"src\", ONE_OF_OUR_BIGGER_IMAGES);\r\n }\r\n }\r\n\r\n \/\/ stop observing this element. Our work here is done!\r\n observer.unobserve(item.target);\r\n };\r\n });\r\n\r\n};<\/code><\/pre>\nOne last step to complete the effect: remove that blur effect from the image once the new source has loaded. A JavaScript event listener waiting for the load<\/code> event on each new image resource can do that for us.<\/p>\n\/\/ remove the lazy class when the full image is loaded to unblur\r\nsource.addEventListener('load', image => {\r\n image.target.closest(\"picture\").classList.remove(\"lazy\")\r\n}, false);<\/code><\/pre>\nWe can make a nice transition that eases away the blur away, with a sprinkle of CSS.<\/p>\n
picture img {\r\n ...\r\n transition: filter 0.5s,\r\n}<\/code><\/pre>\nA little helper from our friends<\/h3>\n Great. With just a little JavaScript, a few lines of CSS and a very manageable dollop of HTML, we\u2019ve created a lazy loading technique which also caters for responsive images. So, why aren\u2019t we happy?<\/p>\n
Well, we\u2019ve created two bits of friction:<\/p>\n
\nOur markup for adding images is more complex than before. Life used to be simple when all we needed was a single img<\/code> tag with a good old src<\/code> attribute.<\/li>\nWe\u2019ll also need to create multiple versions of each image assets to populate each viewport size and the pre-loaded state. That\u2019s more work.<\/li>\n<\/ol>\nNever fear. We can streamline both of these things. <\/p>\n
Generating the HTML elements<\/h4>\n Let\u2019s look first at generating that HTML rather than authoring it by hand each time.<\/p>\n
Whatever tool you use to generate your HTML, chances are that it includes a facility to use includes, functions, shortcodes, or macros. I\u2019m a big fan of using helpers like this. They keep more complex or nuanced code fragments consistent and save time from having to write lengthy code. Most static site generators have this sort of ability. <\/p>\n
\nJekyll lets you create custom Plugins<\/li>\n Hugo gives you custom shortcodes<\/li>\n Eleventy has shortcodes for all of the template engines it supports<\/li>\n There are many more…<\/li>\n<\/ul>\nAs an example, I made a shortcode called lazypicture<\/code> in my example project built with Eleventy. The shortcode gets used like this:<\/p>\n{% lazypicture lighthouse.jpg \"A snazzy lighthouse\" %}<\/code><\/pre>\nTo generate the HTML that we need at build time:<\/p>\n
<picture class=\"lazy\">\r\n <source srcset=\"\/images\/tiny\/lighthouse.jpg\" media=\"(min-width: 1200px)\">\r\n <source srcset=\"\/images\/tiny\/lighthouse.jpg\" media=\"(min-width: 700px)\">\r\n <source srcset=\"\/images\/tiny\/lighthouse.jpg\" media=\"(min-width: 300px)\">\r\n <img src=\"\/images\/tiny\/lighthouse.jpg\" alt=\"A snazzy lighthouse\" \/>\r\n<\/picture><\/code><\/pre>\nGenerating the image assets<\/h4>\n The other bit of work we have created for ourselves is generating differently sized image assets. We don\u2019t want to manually create and optimize each and every size of every image. This task is crying out for some automation.<\/p>\n
The way you choose to automate this should take into account the number of images assets you need and how regularly you might add more images to that set. You might chose to generate those images as part of each build. Or you could make use of some image transformation services at request time. Let\u2019s look a little at both options.<\/p>\n
Option 1: Generating images during your build<\/h5>\n Popular utilities exist for this. Whether you run your builds with Grunt, Gulp, webpack, Make, or something else, chances are there is utility for you. <\/p>\n
The example below is using gulp-image-resize<\/a> in a Gulp task as part of a Gulp build process. It can chomp through a directory full of image assets and generate the variants you need. It has a bunch of options for you to control, and you can combine with other Gulp utilities to do things like name the different variants according to the conventions you choose.<\/p>\nvar gulp = require('gulp');\r\nvar imageResize = require('gulp-image-resize');\r\n\r\ngulp.task('default', function () {\r\n gulp.src('src\/**\/*.{jpg,png}')\r\n .pipe(imageResize({\r\n width: 100,\r\n height: 100\r\n }))\r\n .pipe(gulp.dest('dist'));\r\n});<\/code><\/pre>\nThe CSS-Tricks site uses a similar approach (thanks to the custom sizes feature in WordPress) to auto-generate all of its different image sizes. (Oh yeah! CSS-Tricks walks the walk!) ResponsiveBreakpoints.com<\/a> provides a web UI<\/abbr> to experiment with different settings and options for creating images sets and even generates the code for you. <\/p>\nOr, you can use it programmatically as Chris mentioned on Twitter.<\/p>\n