Skip to content

Instantly share code, notes, and snippets.

@etagwerker
Created December 9, 2022 13:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save etagwerker/d4df6bb16449693a91351aa5aa22fde5 to your computer and use it in GitHub Desktop.
Save etagwerker/d4df6bb16449693a91351aa5aa22fde5 to your computer and use it in GitHub Desktop.
---
layout: post
title: "Optimizing Images - Part 1"
date: 2022-11-21 12:00:00
categories: ["performance"]
author: arieljuod
description: "Serving non-optimized images can make our site feel slow. Here are some techniques to speed things up."
image: "optimizing-images-part-1.jpg"
---
Imagine this scenario: you open a website on your phone, you see an image loading really really slowly, you wonder what's going on and download the image to see more details... turns out the image is 3000x3000px with a size of 1.5Mb!
So, let's talk about different ways to optimize images, common problems, and ways to find these issues early.
<!--more-->
There are essentially 2 types of images we can use on the web: Vector Graphics and Raster Images. This blog post is about optimizations for Raster Images and it is the first of a series of blog posts about optimizing images. We will talk about Vector Graphics in the next one.
## Raster Images
This is what we typically associate with pictures, complex images with details at the pixel level (think of any picture you take with a phone). The file includes information about what to draw in each pixel.
The simplest format is a Bitmap which is basically a list of all the pixels and which color to use for each. Images in this file format are typically really big in file size since there's no optimization applied.
The most popular formats to store raster images with compression are <a href="https://en.wikipedia.org/wiki/JPEG" target="_blank">JPEG</a> and <a href="https://en.wikipedia.org/wiki/Portable_Network_Graphics" target="_blank">PNG</a>, each of them has its uses. There are also older and newer formats, like <a href="https://en.wikipedia.org/wiki/Graphics_Interchange_Format" target="_blank">GIF</a> or <a href="https://developers.google.com/speed/webp/" target="_blank">WEBP</a>.
### Lossy vs Lossless
JPEG uses a `lossy` compression algorithm. This means it can be configured to lose quality (i.e.: drop the information of some pixels and use surrounding ones) in order to save less pixels' data. The lower the quality, the lower the file size, but a really low quality can generate visual artifacts.
PNG uses a `lossless` compression algorithm. This means it will try to compress the pixel data without dropping any information, so the image looks exactly like the original bitmap.
Because of this difference, JPEG is great for pictures and PNG is better for digital drawings and screenshots (to name a few).
There are other formats, like the modern `WEBP`, that support both lossy and lossless compression as well as animations and in some cases with better compression than both JPEG and PNG. Also <a href="https://en.wikipedia.org/wiki/AVIF" target="_blank">AVIF</a> is starting to gain support in browsers.
> WEBP is supported in all modern browsers, you can check <a href="https://caniuse.com/?search=webp" target="_blank">caniuse.com/?search=webp</a> to know if webp and avif are good formats for your use case.
### Optimizing Type, Size, and Quality
The first optimization we should apply to our images is to use the right format, the right size, and the right quality (if using a lossy format).
Regarding size, this will depend on where the image is used. If we have an `img` tag that is intended to have a maximum size of 400px in the site, we should resize the image to that size. No more and no less.
For the format, there's not `one size fits all` rule here. The best option is to open the image in an image editor (or use an online tool) to save it in different formats and compare the results.
Finally, when using a lossy format, we can try different quality settings and compare the results. Again, there's no `one quality fits all` rule; for some images, a quality of 80% or even lower might be acceptable, and for some it may introduce too many artifacts. It depends on the content of the image.
Let's check these options with an example (Chanchi, my dog):
<style>
.showcase {
display: grid;
grid-template-columns: min(400px, 50%) min-content 1fr;
column-gap: .5rem;
}
.showcase img {
grid-column: 1;
grid-row: 1 / 9;
display: none;
}
.showcase input {
grid-column: 2;
}
.showcase input:checked + img {
display: block;
}
.showcase label {
grid-column: 3;
align-self: center;
}
@media screen and (max-width: 600px) {
.showcase {
grid-template-columns: min-content 1fr;
}
.showcase img {
grid-column: 1 / 3;
grid-row: 1;
}
.showcase input {
grid-column: 1;
}
.showcase label {
grid-column: 2;
}
}
</style>
<div class="showcase">
<input type="radio" name="variant" checked="true" id="jpeg100" />
<img src="/blog/assets/images/optimizing-images/chanchi100.jpg" alt="a dog JPEG 100% quality" />
<label for="jpeg100">JPEG 100% quality - 137.5kB</label>
<input type="radio" name="variant" id="png" />
<img src="/blog/assets/images/optimizing-images/chanchi-lossless.png" alt="a dog PNG" />
<label for="png">PNG - 205.3kB</label>
<input type="radio" name="variant" id="webp" />
<img src="/blog/assets/images/optimizing-images/chanchi-lossless.webp" alt="a dog WEBP lossless" />
<label for="webp">WEBP lossles - 157.6kB</label>
<input type="radio" name="variant" id="webp100" />
<img src="/blog/assets/images/optimizing-images/chanchi100.webp" alt="a dog WEBP 100% quality" />
<label for="webp100">WEBP 100% quality - 60.9kB</label>
<input type="radio" name="variant" id="jpeg80" />
<img src="/blog/assets/images/optimizing-images/chanchi080.jpg" alt="a dog JPEG 80% quality" />
<label for="jpeg80">JPEG 80% quality - 27.5kB</label>
<input type="radio" name="variant" id="webp80" />
<img src="/blog/assets/images/optimizing-images/chanchi080.webp" alt="a dog WEBP 80% quality" />
<label for="webp80">WEBP 80% quality - 13.1kB</label>
<input type="radio" name="variant" id="jpeg40" />
<img src="/blog/assets/images/optimizing-images/chanchi040.jpg" alt="a dog JPEG 40% quality" />
<label for="jpeg40">JPEG 40% quality - 13.4kB</label>
<input type="radio" name="variant" id="jpeg20" />
<img src="/blog/assets/images/optimizing-images/chanchi020.jpg" alt="a dog JPEG 20% quality" />
<label for="jpeg20">JPEG 20% quality - 8.2kB</label>
</div>
> You can see how the JPEG image at 80% quality looks good, at 40% quality some noise starts to show up around the hair, and at 20% quality it's noticeably pixelated. Additionally, the webp format in 100% quality is half the size of the JPEG one at 100% quality. The same happens at 80% quality. PNG is clearly a bad idea for an image like this.
## The `<picture>` Element
When we consider responsive design, one single `img` tag is not enough to fully optimize the image we want to display.
An element on our site can have different sizes and limits depending on the size of the device and the design decisions. If we always serve images based on the biggest size needed for a desktop browser we would be serving images that are too big in smaller devices, wasting resources and making the app slower.
Imagine an image that's 1200px wide when the site is displayed in a desktop browser. If we open the site on a phone, the width of the page is (typically) around 400px, so we could deliver a 400px image in these cases instead, in a much smaller file.
We can use the `picture` tag for this, which allows us to specify multiple image sources and different rules so the browser can decide which image to fetch from the server.
Let's continue with the previous example and also add a `medium` size of 800px for the tablet design.
Our original code, with an image tag, would be this:
```erb
<%= image_tag "my-image.webp" %>
```
But we can create different images with different sizes and specify all of them:
```erb
<picture>
<source srcset="my-image-small.webp" media="(max-width: 400px)" />
<source srcset="my-image-medium.webp" media="(min-width: 800px)" />
<%= image_tag "my-image-large.webp" %>
</picture>
```
We need the `img` tag inside the `picture` tag and any number of `source` tags to specify the different rules. If no rule matches, the browser will render the `img` tag with default source.
By doing this, the browser only downloads the image that matches a rule.
Another benefit of this is that we can change not only the size but also the image aspect ratio or even use completely different images to better optimize the cropping instead of relying on the browser and CSS rules for that.
> This example only shows using a CSS media query to check the width of the device, but we can also specify the device's pixel ratio, the preferred color scheme, chose between animated and non-animated images (for users that turned on the reduced motion features), different file types (if we want to use formats like AVIF if the browser supports it), etc.
>
> Read more about the `<picture>` element here: <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture" target="_blank">developer.mozilla.org/en-US/docs/Web/HTML/Element/picture</a>
## Finding non-optimized Images
Finding all the images that can be optimized can be a tedious process to do manually.
We can use <a href="https://developer.chrome.com/docs/lighthouse/" target="_blank">Google Lighthouse</a>'s Performance analysis in both Mobile and Desktop mode and it will give us a list of images that can be optimized and the different optimizations that could be applied:
<img src="/blog/assets/images/optimizing-images/lighthouse.webp" alt="screenshot of the Lighthouse report" />
The `Service images in next-gen formats` section shows the benefit of converting PNG and JPEG images into WEBP or AVIF.
The `Properly size images` list shows images that are too big for the actual display size.
Finally, the `Efficiently encode images` shows the benefit of encoding JPEG images with a quality of 85%.
## Conclusion
Poorly optimized images can generate megabytes of extra unneeded downloads and seconds of waiting. Images are some of the biggest assets we deliver and it's important to keep them under control so our users can have a better experience.
In this article we covered the basics, but these are **MUST DO** optimizations that can mean a really big improvement in performance and are needed before doing other (and, in some cases, complex) optimizations. We can further optimize how we serve images but we will talk about that in the next articles of this series.
Do you need help improving the performance of your application? Check our <a href="https://www.fastruby.io/tune" target="_blank">Tune Report</a> service, we can help you!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment