HTML Images - The <picture> Element

Chapter 27 36 mins

Learning outcomes:

  1. What <img> with srcset can't do
  2. What is the <picture> element
  3. The <source> element
  4. The media and type attributes of <source>
  5. Examples of using <picture>

Introduction

In the previous chapter, HTML Images — The srcset Attribute, we covered the purpose of the srcset attribute on <img> elements and how it can be used to serve images responsively.

Now, srcset on <img> isn't the only way to serve responsive images; another, slightly more complex but much more powerful, approach is to leverage the <picture> element.

In this chapter, we shall learn all about the HTML <picture> element. We shall get to know the purpose for which the <picture> element was created; how to use <picture> to perform what's called art direction; the related notion of <source> elements; and much more.

Truly, an HTML developer's arsenal of skills, especially in terms of writing code for displaying images, is uncomplete without a thorough understanding of the <picture> element. It's a powerhouse in terms of utility and must be understood by each and every HTML developer.

So shall we get into the learning?

What <img> with srcset can't do

As we learnt in the previous chapter, the <img> element possesses a ton of power by virtue of its srcset attribute which can be used to serve different images to different devices depending on their configuration.

To summarize it, srcset is used to specify multiple versions of the same visual image in terms of resolution, along with some surplus information — density descriptors or width descriptors with sizes — which helps the browser decide which one to fetch and render.

Now there are two points worth re-iterating about srcset:

  • srcset is used to and meant to enable us to specify multiple versions of the same image, hoping that the browser will choose the most ideal one for the user's device and browser environment.
  • srcset doesn't mean that we can make guarantees that a particular source will be selected

The second point is very important. It says that srcset is merely a means of providing browsers with some information on the underlying image sources; the final decision of which source to render still comes down to the browser itself, not the HTML.

For instance, the browser might not load a 2x density image on a device with a device pixel ratio of 2 because it is on a very slow network. Or the browser might load the largest-sized image on a very low pixel density screen simply because it somehow happens to have that image already in its cache.

Now, with these points in mind, let's make up a scenario and see whether srcset can help us address it.

Imagine we want to show two different images to the user depending on his device: one is square in proportion with a simple shape and one is rectangle in proportion with some text in addition to the same shape.

The following figure depicts what we needs to accomplish:

Two different images to show on different devices
Two different images to show on different devices

Such a case of serving completely different images, with different proportions, to different devices is commonly referred to as art direction in the realm of the web.

For the sake of discussion, let's say that the square image should be used on devices with a viewport width less than 500px while the rest of the viewports should be filled up with the rectangle image.

And here comes a question.

Can srcset be used in this case to serve the desired image to the user?

Absolutely NOT!

For art direction, where we have completely different images, in terms of proportions, for different devices, srcset is a completely unreliable and impractical choice to make. As stated earlier, it only hints the browser on what to load and what not to load — the final decision stays with the browser.

Apart from this, if we need to specify multiple types of the same image, e.g. JPG, PNG, SVG, srcset is completely useless in that case too. There's just no way for us to be able to define the type corresponding to different image sources in srcset.

In short, there are cases where srcset can't address our responsive image requirements to the full — in fact, not at all..

Fortunately, for such cases, we have a much more powerful element provided to us by HTML — they call it <picture>.

What is the <picture> element?

When we think of an image in HTML, we think of the <img> element. Right? It's the cornerstone of serving images on the web.

Now, before anything, we should note that <picture> is NOT meant to replace <img> as another element to denote an image.

Instead, <picture> was created to wrap an existing <img> and provide it with a whole bunch of different image sources to choose from along with the conditions on when to choose which.

If you're thinking at this point that this is something already accomplished by the srcset attribute on an <img> element (whereby we can specify multiple image sources and define conditions on when to choose which one), then keep reading on, for <picture> can do much more than that. We'll address this question soon in the following discussion.

In plain words, <picture> is a container element with a means of providing an <img> element with a host of information related to different image sources.

The <picture> element provides information to an <img> on different image sources and when to choose which one.

The job of serving an image in HTML still resides with the good and old <img> element — <picture> kind-of like puts it on 'steroids'.

Therefore, we should be clear-cut in comprehending the very fact that <picture> is NOT a replacement/alternative of <img>; it's only a way of providing more contextual information to <img>. (We'll see what exactly this information is shortly below.)

Here's what the HTML standard has to say on the <picture> element:

The picture element is a container which provides multiple sources to its contained img element to allow authors to declaratively control or give hints to the user agent about which image resource to use, based on the screen pixel density, viewport size, image format, and other factors.

Putting an <img> inside a <picture> isn't a preferential thing; it's a requirement. Every <picture> element must have an <img> in it.

Following is a minimal example of how a <picture> element should look:

<picture>
   <img src="shapes.png">
</picture>

Note that, for brevity, we've only included the src attribute on <img>. On a real website, you'd definitely want to include other attributes here — the likes of alt, loading, or maybe srcset, and so on.

Alright, so the <picture> element wraps up an <img> and provides information to it related to different image sources.

But where does this information related to different image sources go? Does it go into any attributes on <picture> or any elements inside it?

Well, it goes into some elements — <source> elements.

<picture> itself represents nothing!

The <picture> element itself does NOT represent anything on the webpage, unlike let's say a <p> element, or an <article>, or a <table> — it merely provides information to its contained <img>.

This is important to know because after writing out a <picture> in code, we might go on and apply styles to it, thinking that they'll be applied to the image rendered by the <picture>. That's NOT the case.

If we need to modify the styles of an image rendered for a <picture>, we need to target its underlying <img> element.

The <source> element

The <source> element is meant to define a media source in HTML, e.g. the source of an image, an audio, or video.

When we'll learn about the <audio> and <video> elements later on in this course, we'll encounter <source> again. For images, <source> works a little differently, and this is what we'll explore in the following discussion.

The <source> element can go inside a <picture> and when that happens:

The <source> element defines a source set for the corresponding <img> (in the <picture> element).

In terms of attributes, <source> works quite similar to <img>. That is, <source> can define one image source or multiple image sources, varying in their resolution (similar to setting srcset on <img>).

In this way, each <source> element, in a collection of multiple <source> elements, can be used to represent a different image at multiple resolutions.

Technically, a <picture> element can have as many <source>s as we want to but this number is typically in the range 2 - 5.

The attributes that can be set on <source> when it's inside <picture> include:

  • srcset — works identical to srcset on <img>.
  • sizes — works identical to sizes on <img>.
  • media — defines a media query condition for the underlying <source>.
  • type — defines an image MIME type condition for the underlying <source>.
  • width — works identical to <width> on <img>.
  • height — works identical to height on <img>.

Of these attributes, the three main ones are srcset, media, and type.

After srcset is set on a <source> element, at least one of the two attributes, media and type, is required. This is because both these attributes help the browser determine when to and when not to choose a given <source>.

Let's go over these three key attributes.

srcset

When <source> is part of a <picture> element, we specify its image source via the srcset attribute. Even if we have just one image source for the <source>, it goes into the srcset attribute.

When inside a <picture>, <source> can NOT have a src attribute. This attribute is reserved exclusively for instances when <source> is inside <audio> or <video>. For <source> inside <picture>, we can only use srcset.

This srcset attribute of <source> has the exact same semantics as the srcset attribute of the <img> element, which we covered in fine detail in the previous chapter.

Consider the following code:

<source srcset="some-image.png">

Here we refer to an image some-image.png using the srcset attribute on <source>. Note that we don't have any descriptor mentioned here next to the source URL.

The best thing about srcset is that when we have just one image source, we don't need to worry about specifying any descriptors — in such a case, the browser assumes a density descriptor of 1x.

Apart from this, if srcset contains sources annotated with width descriptors, we can use the sizes attribute of <source> to define the corresponding image slot size, as we do with the sizes attribute on <img>.

media

The media attribute contains a CSS media query condition for which the given <source> should be used.

It allows us to lay out conditions on the width or height of the viewport, the width or height of the device, the orientation of the device (landscape vs. portrait), and so on.

For example, if we want to specify an image source that should only load when the viewport is less than or equal to 500px wide, we'll use the following <source> with the given media:

<source media="(max-width: 500px)" srcset="...">

(max-width: 500px) here represents a CSS media query definition. max-width relates to the width of the viewport, placing an upper bound onto it, i.e. it matches all viewport widths not greater than 500px (CSS pixels).

The complement of max-width in CSS is min-width. If we want to place a lower bound on the viewport width, we can use min-width:

<source media="(min-width: 360px)" srcset="...">

This <source> yields a match whenever the viewport is greater than or equal to 360px wide.

type

The type attribute specifies the given image's type, for e.g. JPG, PNG, SVG, etc. Specifically, type gives the MIME type of the image.

The table below showcases a couple of well-known image formats alongside their MIME types:

Image typeMIME type
JPGimage/jpg, image/jpeg
PNGimage/png
SVGimage/svg+xml
AVIFimage/avif

By reading the type of a <source> (inside <picture>), the browser can determine whether or not it can render the given image source.

Now that we know the basic idea behind <picture> and <source>, let's see how exactly the browser makes sense of a <picture> element and picks the most appropriate <source>.

How <picture> works?

When the browser encounters the <picture> element, comprised of a set of <source> elements followed by an <img>, it has to determine which <source> to choose, or either fall back to the given <img>.

This process is quite simple in practice and driven by either of two key attributes on each <source>: media and type.

So here's how the image selection happens for a <picture>:

  • The browser reads the first <source> element inside the <picture> and evaluates its condition, which is either in the form of the media attribute or the type attribute.
  • If the condition matches, the <source> is selected and its srcset is used by the later <img> element.
  • Otherwise, these steps are repeated for the next <source>.
  • If neither <source> matches, or there is just no <source> element inside the <picture> at all, the <img> element is rendered based on its own attributes.

All in all, the browser sequentially walks through the <source>s until one seems to be the perfect choice to make, in which case its fed into the underlying <img>; otherwise, the <img> is rendered as usual.

Pretty basic, isn't it?

The order of <source> matters in <picture>

Now, although apparent by the previous series of steps, there is one crucial point to note regarding <source> inside <picture>.

That is, the order of <source> matters.

This is simply because the browser sequentially goes through the given <source> elements and selects one as soon as it finds that <source> to be the perfect choice.

So, if there are any conditions on preceding <source>s that will always match, e.g. a type of "image/jpg", the following <source>s will never ever be reached.

Therefore, make sure that the conditions of a given <source> element don't encompass the conditions of following <source>s so as to not override it.

If a <picture> has <source>s with the media attribute, which typically means that the final image depends on the configuration of the device's viewport, the browser will re-run the above steps as the viewport's dimensions change (for e.g. by changing orientation of the device, or manually resizing the browser window, etc.)

This is evident in a note under the description of the <source> element in the HTML standard, as stated below:

...when using the picture element, the user agent will react to changes in the environment.

In that way, as the viewport changes, the <img> element of a <picture> will also change to the most appropriate image.

Thus, with a <picture> having multiple <sources> along with their media or type, the browser no longer gets mere hints on which image to choose, as is otherwise the case with srcset; it gets commands.

In other words, now the HTML gets to decide which image to choose.

Obviously, at the end of the day, it's always the browser that chooses the image; the statement that "the HTML gets to decide the image" emphasizes on the fact that <picture> allows the HTML to completely influence this decision of the browser. It's a quick and concise way to highlight the power of <picture> over the srcset attribute.

Let's consider some examples to help clarify all what we've learnt thus far.

Examples of <picture>

In this section, we'll consider two typical use cases of <picture>: doing art direction; and serving the most appropriate image from a list of different image types.

Doing art direction

First, let's try to solve the task laid out at the start of this chapter of showing a square image or a rectangle image depending on the user's device.

Here are the two images that we'll be dealing with, along with their names:

The square image, pentagon-square.png
The square image, pentagon-square.png
The rectangle image, pentagon-rect.png
The rectangle image, pentagon-rect.png

Here's the task again: We need to show the image pentagon-square.png when the viewport width is less than or equal 500px, or otherwise the image pentagon-rect.png.

Let's get to write some HTML.

First things first, we'll create a <picture> element and add an <img> inside it, representing the actual image rendered by the browser for the <picture>:

<picture>
   <img src="pentagon-rect.png" alt="Pentagon shape image">
</picture>

In our case, we use the pentagon-rect.png source for the <img> because it seems a good fit to act as the default case, i.e. when the browser's viewport isn't less than or equal to 500px wide.

pentagon-rect.png in <img> is a good choice also because if the browser doesn't support <picture> (which is quite uncommon these days), we'd want to render the larger image and not the smaller one (pentagon-square.png).

Next up, we'll set up the <source>s. Having already mentioned pentagon-rect.png in <img>, this means setting up only one <source>, for the square image:

<picture>
   <source media="(max-width: 500px)" srcset="pentagon-square.png">
   <img src="pentagon-rect.png" alt="Pentagon shape image">
</picture>

Two things to note here:

  • The image source in <source> is laid out in the srcset attribute. Recall from the discussion above that src can't be set on <source> when it is inside <picture>.
  • The media attribute can also go after the srcset; there's no requirement of it coming before srcset.

Now, let's see the output:

Live Example

Try resizing the browser window in the page above and see the image changing in situ. This is the <picture> element in action.

Let's add some more bells and whistles to this <picture>.

Currently, the image is rendered at its natural width. As we learnt in HTML Images — Basics, this is problematic if the user's device has a viewport less than the image's width.

Do you recall what will happen if this is the case? Well, the image will overflow out of the viewport, giving an extremely disliked horizontal scrollbar to the webpage. To remedy this, we can leverage the width style property from CSS on the <img>.

This is done as follows:

<picture>
   <source media="(max-width: 500px)" srcset="pentagon-square.png">
   <img src="pentagon-rect.png" alt="Pentagon shape image" style="width: 100%">
</picture>

Let's see the output now:

Live Example

Much better.

Next-gen image formats

Over the years, digital images have garnered a lot of research and interest in terms of developing new and ever more sophisticated mechanisms of storage and representation. This makes sense because the digital world relies a lot on digital imagery.

On the web, this fact becomes even more pronounced as images have to be delivered over the network. Better image encoding mechanisms potentially mean less space consumed by image files and, likewise, less network bandwidth expended in delivering these files.

Some common image formats that almost every computer user is aware of include PNG and JPG (or JPEG). There's even SVG.

While PNGs and JPGs serve most user's needs, sometimes they can get quite heavy in terms of image sizes. 5MB might not look like a really big file size but when multiplied with even just ten images, it quickly adds up.

On the web, we are always striving to deliver experiences that consume the least number of bytes over the network because you know, more bytes typically mean more loading — and users aren't any longer accustomed to waiting for seconds while things are loading.

In recent years, many new next-gen image formats have sprung up that can be extremely efficient in terms of reducing image sizes down to kBs while still keeping the quality top-notch. The most popular of these formats are AVIF and WebP.

We won't go into the detail of these image types, and rather keep the discussion focused on seeing how the <picture> element can be used to switch between different image types.

Let's assume that we have an image available in three types: AVIF, WebP, and PNG. For the sake of argument, suppose the following file size breakdown:

FormatFile size (kB)
AVIF30
WebP56
PNG340

Clearly, we'll want to deliver the AVIF file to browsers that support it; or otherwise the WebP file to browsers that don't support AVIF but support WebP; or otherwise fall back to the PNG file (which almost all browsers understand). There are a lot of potential bandwidth savings in delivering AVIF or WebP.

To solve this task, we can easily utilize the <picture> element as follows:

<picture>
   <source srcset="some-image.avif" type="image/avif">
   <source srcset="some-image.webp" type="image/webp">
   <img src="some-image.png">
</picture>

The first <source> deals with the AVIF type. The second one deals with the WebP type. The <img> denotes the fallback image to use in case the browser can't understand the AVIF and WebP formats.

Most importantly, the way the browser is able to determine the types of the first two images is via their type attributes. The first type, "image/avif" corresponds to the MIME type of the AVIF format. Similarly, "image/webp" corresponds to the MIME type of the WebP format.

For <img>, we don't need type because the browser isn't concerned with knowing the type of the underlying image source given in <img> — it's all that's left to pick and so the browser must load it.

It's crucial to keep in mind that file extensions don't specify the type of a given resource; they're part of a file's name and merely help us determine its type just by reading the name. A file named image.png, for example, can well be an AVIF file. So don't skip type in <source> assuming that the browser will infer the type of each image from its extension.