DEV Community

Moray Macdonald
Moray Macdonald

Posted on

Making better images with HTML5 and React

I love HTML5. I love its semantic elements. I love its accessibility features. Mostly, I love the fact that its designers clearly looked at all the problems that web developers were having with HTML4, and all the libraries and hacks we were using to get around them and decided "Right. We'll fix that." And for the most part they did!

One of the things that every web developer will encounter is how to show images to the user. Unfortunately, displaying images is a lot more complex than <img src="cat.jpg">, when you start considering all the different people who'll be using your site - us devs have nice big screens and fast internet connections, but a user on a narrow mobile screen with a slow connection is not going to want to wait to load your gorgeous hi-res header image. And that's before we get to my gran, who still has a beige tower PC and a web browser that's barely heard of PNG, let alone fancy things like WEBP and AVIF!

Fortunately, HTML5 is here to the rescue with its very handy <picture> element! Here's what it looks like:

<picture>
  <source
    srcset="/static/cat-1024x768.webp"
    type="image/webp"
    media="(min-width: 800px)">
  <source
    srcset="/static/cat-1024x768.jpg"
    type="image/jpeg"
    media="(min-width: 800px)">
  <source
    srcset="/static/cat-800x600.jpg"
    type="image/jpeg">
  <img
    src="/static/cat-1024x768.jpg"
    title="You should really title your images!"
    alt="You should alt text them too. Screen readers and crawlers will thank you!">
</picture>
Enter fullscreen mode Exit fullscreen mode

This looks far more complex than <img src="cat.jpg">, but what the <picture> element does is actually very clever and very simple. It looks at all the possible things it can display (the <source> elements), and then picks the most appropriate one to load and display to the user using the supplied <img> element.

So for the example above, the browser will go through the following logic:

  1. If I support WEBP images, and is my width greater than 800px? If so, pick the first <source>
  2. If I don't support WEBP but my width is greater than 800px, I'll pick the second <source>
  3. If I'm less than 800px wide I'll pick the last <source>
  4. If I'm really old and don't support HTML5, I'll just render the <img> tag and use whatever is in its src attribute.

This gives us a HUGE amount of flexibility to show the most appropriate images for whatever browser our users are using, so after you've finished reading this article, have a look at the documentation - there's some great stuff you can do with pixel density selectors and more!

The most important element in the <picture> is still the <img> element - in fact, it's required otherwise old browsers can't render anything! That <img> controls how the final image is displayed - so that's what you should stick your CSS classes, titles and alt text to.

So that's how the HTML works, but how can we make that work in React? Very simply, as it turns out! In most of my web projects I create a component like the one below. Please note that it doesn't expose all the features of <picture>, only the ones I most commonly use:

function Picture({
  alt,
  className,
  fallback,
  sources,
  title,
}) {
  // I don't like typing out the <source> elements in full
  // so I just pass in the key properties
  const sourceElements = sources ? sources.map(s => {
    if (s.width) return (<source srcset={s.src} type={s.mime} media={`(min-width: ${s.width}px)`}/>)
    return (<source srcset={s.srcset} type={s.mime} />);
  }) : null;

  return (
    <picture>
      {sourceElements}
      <img src={fallback} className={className} title={title} alt={alt} />
    </picture>
  );
}
Enter fullscreen mode Exit fullscreen mode

And to produce the example I showed at the start I use it like so:

import imageJpg from '../img/cat-1024x768.jpg';
import imageSmallJpg from '../img/cat-800x600.jpg';
import imageWebp from '../img/cat-1024x768.webp';

function HowToUseIt() {
  return (
    <Picture
      title="You should really title your images!"
      alt="You should alt text them too. Don't be lazy!"
      fallback={imageJpg}
      sources={[
        { src: imageWebp, mime: 'image/webp', width: 800 },
        { src: imageJpg, mime: 'image/jpg', width: 800 },
        { src: imageSmallJpg, mime: 'image/jpg' }
      ]}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

Easy to read and easy to use! Drop it into your next project and hopefully watch your Lighthouse scores increase...

Thanks for reading! Let me know if you found it useful, and if you've got any improvements to make to my little component 😄

Top comments (0)