Web Images
The sum of my knowledge about web-based images.
Table of Contents
- Image Formats
- Optimization Tools
- Lazy-loading
- Responsive Images
- Serve Images in Modern Formats
- Art direction
Image Formats
- WEBP - Newer format. Supports transparency, lossless quality, and animations. Supported in all modern browsers and WordPress 5.8 and above. This should be your default format.
- AVIF - Newest format. Better than
.webp
in all respects, but has limited browser support. (Chrome/Opera) - JPG - Old and bloated. Only use as fallback or if IE11 support is required.
- SVG - For simple geometric shapes, like icons and logos.
- PNG - For when transparency and/or lossless quality is required.
Optimization Tools
ImageOptim (Mac/Node/CLI/Web)
ImageOptim is a great choice for optimizing PNG and JPGs. There's even a ImageOptim-CLI and online version available.
Sharp (Node/CLI)
The typical use case for Sharp is to convert large images in common formats to smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions. There's even a CLI available.
Simple resize
The following example will resize all JPGs to 1920px, place them in a /thumbs
directory, while preserving EXIF data:
npx sharp-cli resize '1920' --input '*.jpg' --output './thumbs' --withMetadata
Squoosh (Node/CLI/Web)
Squoosh is an open-source image compression app from Google. There's also a node-based Sqoosh CLI which is really elegant.
Simple convert
Convert all .jpg
images to .webp
and .avif
:
npx @squoosh/cli *.jpg --avif --webp
Resize, optimize, and convert
Convert, resize, auto-optimize, and append _400
to the filename:
npx @squoosh/cli *.jpg --webp --avif auto --resize {width:400} --suffix _400
Using
auto
will increase the quality, but also the file size.
ImageMagick (CLI)
ImageMagick is a free and open-source CLI-based image processing tool written in C. I'd argue it's the defacto library on most web servers. It's very powerful, but the documentation is difficult to understand and the syntax is cumbersome. That said, here are some commands that I've used...
Simple resize
Resize all .jpg
images to 400px wide, set the quality to 70%, append -400
to the end of the filename:
magick *.jpg -resize 400x -quality 70% -set filename:area "%t-%w" "%[filename:area].jpg"
ImageMagick will remove all EXIF data.
Use as a script
ImageMagick really shines when it's used for scripting. Below is a bash function to create some images for a hero component that I want to upload to a website. It requires the ImageOptim-CLI.
- Add the following to
~/.bashrc
or~/.zshrc
- Restart your terminal
- Run
heroimage *.jpg
This is a destructive operation, so backup your original files first!
heroimage() {
# Convert any PNGs to JPGs.
magick mogrify -format jpg *.png
# Create a list of all JPGs in the current directory.
filelist=`ls | grep '.jpg'`
# Loop over the list and create image "hero images" of various sizes.
for image_file in $filelist
do
imagename=`convert $image_file -format "%t" info:`
magick convert $image_file -write +delete \
(+clone -resize 960x540! -quality 80% -strip -insterlace Plane -set filename:filesize "%wx%h" "./$imagename-$[filename:filesize].jpg" +delete ) \
(+clone -resize 480x270! -quality 80% -strip -insterlace Plane -set filename:filesize "%wx%h" "./$imagename-$[filename:filesize].jpg" +delete ) \
-resize 1920x1080! \
-quality 80% \
-strip \
-interlace Plane \
-set filename:filesize "%wx%h" "./${imagename}-%[filename:filesize].jpg"
done
# Run final optimizations through ImageOptim.
imageoptim ./
}
WordPress Plugins
- EWWW (Free)
- ReSmush.it (Free for images up to 5MB)
- Cloudinary (Offers free tier)
- Jetpack's CDN (Free, but requires WordPress.com account. No image optimization)
Hosted, On-demand Image Manipulation
- Cloudinary (Offers free tier)
- Thumbor (Open-source, Cloudinary alternative)
- Cloudflare (Free tier doesn't include image optimization)
Lazy-loading
Browser level
<img alt="A lazy loading image" src="image.webp" loading="lazy" />
Gotchas
- Safari 14 and 15 have support for
loading="lazy"
, however it must be enabled by the user. (Develop --> Experimental Features --> Lazy image loading) - If the image is an above-the-fold-hero, be sure to preload it.
- Don't use
loading="lazy"
if the image is above the fold. (it increases LCP score)
The example below would help decrease the LCP score for hero images above the fold.
<html>
<head>
<!-- Instruct the web browser to download this image ASAP! -->
<link rel="preload" as="image" href="/hero-image.webp" />
</head>
<body>
<!-- Instruct the web browser to display this image ASAP! -->
<img alt="A hero image" src="hero-image.webp" loading="eager" />
</body>
</html>
Further reading: https://web.dev/fast/#lazy-load-images-and-video
Intersection Observer
<img
alt="I'm an image!"
class="lazy"
data-src="image-1x.webp"
data-srcset="image-2x.webp 2x, image-1x.webp 1x"
src="placeholder-image.webp"
/>
document.addEventListener('DOMContentLoaded', function () {
var lazyImages = [].slice.call(document.querySelectorAll('img.lazy'))
if ('IntersectionObserver' in window) {
let lazyImageObserver = new IntersectionObserver(function (
entries,
observer
) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target
lazyImage.src = lazyImage.dataset.src
lazyImage.srcset = lazyImage.dataset.srcset
lazyImage.classList.remove('lazy')
lazyImageObserver.unobserve(lazyImage)
}
})
})
lazyImages.forEach(function (lazyImage) {
lazyImageObserver.observe(lazyImage)
})
} else {
// Possibly fall back to event handlers here
}
})
Further reading: https://web.dev/lazy-loading-images/
Responsive Images
The example below will display a 400px version of the image on mobile devices, and the full size image for tablet and desktop.
<img
alt="an image of a thing"
decoding="async"
height="567"
loading="lazy"
sizes="(max-width: 600px) 400px, 768px"
srcset="image-400.webp 400w, image.webp 768w"
src="image.webp"
width="755"
/>
Further reading:
- https://web.dev/serve-responsive-images/
- https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images
Serve Images in Modern Formats
The example below uses <picture>
to serve images in the newsest formats while gracefully falling back to a .jpg
.
<picture>
<!-- Try and load the .avif image -->
<source type="image/avif" srcset="image.avif" />
<!-- If .avif isn't supported, load the .webp image -->
<source type="image/webp" srcset="image.webp" />
<!-- If neither .avif nor .webp are supported, load the .jpg image -->
<img
alt="an image of a thing"
decoding="async"
height="300"
loading="lazy"
src="image.jpg"
width="400"
/>
</picture>
You can only set image attributes on the
<img>
tag!
Gotchas
- The net gain is smaller images sizes, but at the expense of excessive DOM size. Don't be surprised if Lighthouse barks at you for having too much HTML!
- You're also now juggling 3 different image formats for the same image. This can increase exponentially if you have multiple image sizes for each image.
- You're better off using the responsive image systax with
webp
.
Further reading: https://web.dev/uses-webp-images/
Art direction
Display a different image based on the viewport width:
<div class="align-left">
<picture>
<!-- on mobile, center and stack, display the 400px image -->
<source
type="image/webp"
media="(max-width: 600px)"
srcset="image-400.webp"
/>
<!-- on tablets, continue to center and stack, but display a higher resolution image -->
<source
type="image/webp"
media="(min-width: 601px) and (max-width: 1023px)"
srcset="image.webp"
/>
<!-- on desktops, align the image left, and display the 400px image again -->
<source
type="image/webp"
media="(min-width: 1024px)"
srcset="image-400.webp"
/>
<!-- for older browsers, just display a .JPG -->
<img
alt="an image of a thing"
decode="async"
height="300"
loading="lazy"
src="image.jpg"
width="400"
/>
</picture>
</div>
Chances are the
.jpg
from the<img>
tag wont load (unless it's an old browser), you still need to set the attributes!
Gotchas
- Like with Serving Images in Modern Formats, Art Direction introduces a lot of extra HTML in the form of nested DOM elements. Lighthouse is likely to bark at you for this, so use sparingly!
Further reading: https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images#art_direction