Smarter, Lighter, Better Images: A Guide to OptimizationPublished on 30 Jan 2021 10 min read
Do you know how big the images displayed on your website are? When you open a page, the browser starts downloading a bunch of files in order to display it. Research shows that images are the most requested asset type and take up more bandwidth than any other resource. So, making sure they are as small as they can be can greatly improve the load times for your website. (spoiler alert: mine’s become 85% faster!)
More efficient formats #
For a long time, JPGs and PNGs have been our standard image formats. However, they are not optimized for the web - their quality is often unnecessarily high and the download size is too big. Over time, many new formats have appeared, but two of them have become quite notable: WebP and AVIF.
WebP has been introduced in 2010, and has slowly gained adoption since then. Since 2020, WebP is now supported in all modern browsers. AVIF was launched in 2020, and its adoption has been faster. As of January 2021 it is supported by all Chromium-based browsers, and Firefox will start supporting it on version 86, scheduled to release before March.
But how do we use those shiny new formats if not all browsers support them?
With the HTML
<picture> element, we can make the browsers do the work for us. We can declare multiple sources for the same image, and the browser will try to load them in order. If they do not support a format, they will immediatelly jump to the next one.
So, what we want to do is declare those different sources in the following order:
AVIF -> WebP -> JPG (or PNG)
If you look at the resulting HTML in your website, you can see that the
<img> element has a
src defined, but when you hover over it, it shows what is the actual file that’s being loaded. If you’re on a supported browser, it will have loaded the AVIF file. If you’re on Safari, it will have loaded the WebP one. Otherwise, if you’re using IE or something (I’m sorry), the original JPG or PNG file will be loaded.
Load smaller images #
You can optimize even further than that. See, in my example, I am loading an image with a width of 1200px, however, the size it’s being displayed is only 319px wide. The
srcset property supports different widths to load, depending on the screen size.
srcset property is smart. As the name implies, it is a set of sources, not just a single one. When we declare multiple file paths and add a width unit besides it, the browser looks at this data and tries to display the smallest possible image.
On the code snippet above, the browser will follow this: If the size of the displayed image (on the page) is smaller or equal to 380px, it will load the file with 380px of width. Otherwise, it will try to load the next declared path (640px).
However, not all parts of this process are smart. The browser cannot know what is the final size of the image on the page before it actually loads it. Which is why the
sizes property exists. Let’s see how it works:
sizes property defines what rule the browser will use to get the width it uses to choose the correct file in
srcset. The default value is
100vw. That means that, to check what width the image will have, the browser just gets the width of the browser window. If we know the exact size the image will have on load, we can declare it here, or if we don’t know the exact size, we can estimate. We can use media queries to help us specify the sizes better as well.
Check out the value on the example:
(max-width: 979px) 100vw, 640px. What that code does is: if the width of the viewport is equal or smaller than 979px, use 100vw. Else, use 640px.
It is easier to understand if we visualize it like this:
Of course, different websites have different needs and situations. Make sure to adapt the code to your specific need.
It’s also worth noting that most phones use a HiDPI mode. This means that even though the reported width for the phone above is 375px, the browser will likely use a higher resolution to load the images (usually 2x), in order to serve a higher quality image.
Lazy Loading and Async Decoding #
You might have noticed the
decoding="async" attributes in the code above. Those are relatively new options that are part of an ongoing effort to make the web faster.
decoding="async" tells your browser it can try to parallelize loading your image. When your page is loading, it tries to decode both text and images at the same time. On lower-end devices though, decoding heavy images can take a while, and this might block the rendering of the rest of the content. With this option, the browser will try to proceed rendering the rest of the content and render the image later. This can be a great improvement to perceived performance.
loading="lazy" is probably the most important of the two. It is an easy way of telling the browser to only load the images when they get close to appearing in the viewport. There is a threshold that is defined by the browser that controls how close it needs to be before it gets loaded, so you don’t have to worry about them not showing up if the user scrolls fast. This ensures that the initial load of the website is as lean as it can get, improving perceived performance and also saving you some money on server requests.
Results In Practice #
Since I like using my own website and blog as a testbed for new stuff that I learn, I have applied these optimizations to it. The results were incredible!
Note: after doing some tests, I have decided that the benefits of serving differently-sized images on my website were too small to justify the extra effort of handling all these extra images. So, the only optimizations I have really applied were the optimized file formats, lazy loading and async decoding. I also chose PNG as fallback type instead of JPG because some of my images have transparency in them, which JPG does not support.
The following data is taken from the home page of the website, since it has a lot of images:
The total download size decreased by a whopping 85%!! That’s an incredible difference, with no noticeable difference in quality. Your results may vary, as they depend on how much of your website’s size is images.
Before the changes, out of 1.6MB total, 92% of it were images, 5% were fonts, 1% was HTML, and the remaining 2% were of JS and other things like the web manifest.
Now, out of 249kB, just 24% of it are images. Fonts now make up 27% of page size, and are likely the target of a future optimization post!
The Hard Part #
The hardest part of this process is converting the images to all necessary formats and sizes. It is a lot of effort to do manually even for a single image, and even worse if you’re trying to optimize existing images like I was.
Generating the Images #
For my needs, I have developed a NodeJS script that uses the Sharp library to do the magic for me. It accepts as parameters a source and a target folder, input file types (what files it will look for in the source folder), output file types (what types it will convert to), as well as the desired widths.
The script is at the time of writing this currently usable only via command line. I have plans to turn this into a part of the build process of my website, with a GitHub Action, so that I don’t have to run it manually.
So, to check out how to use the script, please check out its GitHub page for up-to-date instructions.
Using the images #
To make this setup work, I had to do some changes on how images were used on my website.
- All the images on my website were initially in a folder called “images”, with various subfolders;
- The images I wanted to convert were all in either PNG or JPG formats;
Modifications I did:
- I have created a folder called “optimized-images”, where all the converted images are saved automatically by my script;
- I have created a component to centralize all image-loading logic. With Jekyll, I just had to create an HTML file in the
_includesfolder, but how you do this might vary depending on what technology you use;
- This component receives as parameters: the relative file path, the filename (without file extension), and the alt text;
And to use this component inside another page:
My folder structure looks like this:
Wrapping Up #
With such amazing results, it’s hard not to recommend you to optimize the images in your website ASAP. There sure are more approaches and optimizations that can be done besides the ones presented on this article, as the web world is always changing. But optimizations are a great way of showing respect to your users (as well as gathering more of them). Your site loads more quickly, and it uses less data and resources.
As front-end developers, we must acknowledge that all we write runs on someone else’s computer, the user’s. So it is important that we respect them and make sure we just use the resources we need to.
I hope you enjoyed reading this! Take care and happy coding!
How to fix your Favicons4 min read
Favicons kinda suck. They should be a simple icon that identifies your webpage on a bunch of scenarios, i.e. the...Click to read more…
Spicing Up your GitHub Profile with HTML and CSS3 min read
Last year, GitHub added a new cool feature for the user profile. You can now add a README file to...Click to read more…
CSS Scroll Snapping - Improve Scrolling without JS4 min read
CSS is evolving constantly and the past few years have brought us amazing stuff. It is getting easier to make...Click to read more…