Responsive Images with HTML picture

Hugo Partial for controlled output of image sizes depending on monitor resolution and viewport size.

In my blog post I describe the source code, the corresponding SCSS and give tips for testing the Hugo localhost browser output at @1 and @2 monitor resolutions in a network.


{{ with (.context.Resources.GetMatch "article-img") }}
  {{ $images := newScratch }}
  {{ $images.Set "css-class" $.cssclass }}

  {{ $images.Set "img@2" ($.context.Resources.GetMatch "article-img") }}
  {{ $width := ($images.Get "img@2").Width }}
  {{ $width := printf "%dx" (div $width 2) }}
  {{ $images.Set "img@1" (($images.Get "img@2").Resize $width) }}

  {{ $images.Set "img768@2" (($images.Get "img@2").Resize "1536x") }}
  {{ $images.Set "img768@1" (($images.Get "img@1").Resize "768x") }}

  {{ $images.Set "img576@2" (($images.Get "img@2").Resize "1152x") }}
  {{ $images.Set "img576@1" (($images.Get "img@1").Resize "576x") }}

  {{ $images.Set "img400@2" (($images.Get "img@2").Resize "800x") }}
  {{ $images.Set "img400@1" (($images.Get "img@1").Resize "400x") }}

  {{ $images.Set "img320@2" (($images.Get "img@2").Resize "640x") }}
  {{ $images.Set "img320@1" (($images.Get "img@1").Resize "320x") }}

  {{ $images.Set "imgx150@2" (($images.Get "img@2").Resize "x300") }}
  {{ $images.Set "imgx150@1" (($images.Get "img@1").Resize "x150") }}

  {{ if eq ($images.Get "css-class") "img-articleimg" }}
    <picture>
      <source media="(min-width: 992px)" 
              srcset='{{($images.Get "img@1").RelPermalink}} 1x,
                      {{($images.Get "img@2").RelPermalink}} 2x'>
      <source media="(min-width: 768px)" 
              srcset='{{($images.Get "img768@1").RelPermalink}} 1x,
                      {{($images.Get "img768@2").RelPermalink}} 2x'>
      <source media="(min-width: 576px)" 
              srcset='{{($images.Get "img576@1").RelPermalink}} 1x,
                      {{($images.Get "img576@2").RelPermalink}} 2x'>
      <source media="(min-width: 400px)" 
              srcset='{{($images.Get "img400@1").RelPermalink}} 1x,
                      {{($images.Get "img400@2").RelPermalink}} 2x'>
      <source media="(min-width: 320px)" 
              srcset='{{($images.Get "img320@1").RelPermalink}} 1x,
                      {{($images.Get "img320@2").RelPermalink}} 2x'>
      <img src='{{- ($images.Get "img@1").RelPermalink -}}' class='{{- $images.Get "css-class" -}}' alt="{{ $.context.Title }}" />
    </picture>
  {{ else if eq ($images.Get "css-class") "img-teaserimg" }}
    <picture>
      <source media="(min-width: 992px)" 
              srcset='{{($images.Get "imgx150@1").RelPermalink}} 1x,
                      {{($images.Get "imgx150@2").RelPermalink}} 2x'>
      <source media="(min-width: 768px)" 
              srcset='{{($images.Get "img768@1").RelPermalink}} 1x,
                      {{($images.Get "img768@2").RelPermalink}} 2x'>
      <source media="(min-width: 576px)" 
              srcset='{{($images.Get "img576@1").RelPermalink}} 1x,
                      {{($images.Get "img576@2").RelPermalink}} 2x'>
      <source media="(min-width: 400px)" 
              srcset='{{($images.Get "img400@1").RelPermalink}} 1x,
                      {{($images.Get "img400@2").RelPermalink}} 2x'>
      <source media="(min-width: 320px)" 
              srcset='{{($images.Get "img320@1").RelPermalink}} 1x,
                      {{($images.Get "img320@2").RelPermalink}} 2x'>
      <img src='{{- ($images.Get "imgx150@1").RelPermalink -}}' class='{{- $images.Get "css-class" -}}' alt="{{ $.context.Title }}" />
    </picture>
  {{ end }}
{{ end }}

The partial is called as follows:

{{ partial "img-post" (dict "context" . "cssclass" "img-articleimg") }}

For every image you will generate 10 new images? This is a lot to process.

Reduce it to a single srcset with size factors of 1.5 to 2.0 for every step and end up with max 4…5 images.
This reduces the HTML size and every browser can fetch the image with the best resolution.

Cheers

1 Like

Hhmm, good point. Generating with Hugo is lightning fast. But my blog isn’t very big yet either.

You mean only one image per media querie? The difference in file size between @2 and @1 images is significant. The HTML code will be a little bigger, but it is many times smaller than the difference between @2 and @1 images. The browser only loads the image that is relevant for the user browser. Only this image goes over the line.

As I said, the generation process with Hugo is very fast. I therefore see nothing that speaks against this solution. But maybe I’m just not understanding this correctly. Discuss with me.

I go without media queries.

In my theme config

[Params.Images]
    maxSize               = 1480
    setSizes              = [ 480, 800, 1200 ]
    sizes                 = "sizes=(max-width: 480px) 90vw, (max-width: 960px) 80vw, 1480px"

it is easy to add more sizes …

and render-image.html termplate

{{ $image := .Page.Resources.GetMatch (printf "%s" (.Destination | safeURL)) }}
{{ $p := .Page }}
{{ $alt := (.PlainText | safeHTML) | default "picture" }}{{ $caption := "" }} {{ with .Title }}{{ $caption = . | safeHTML }}{{ end }}
{{ $iw := $image.Width }}{{ $ih := $image.Height }}{{ $ms   := site.Params.Images.maxSize | default 1024}}
{{- if or (ge $iw $ms) (ge $ih $ms) -}}{{ $image = $image.Fit (print $ms "x" $ms ) }}{{- end -}}
{{ $srcset := slice }}{{ range site.Params.Images.setSizes -}}
{{ if lt (mul . 1.2) $iw -}}{{ $size := print  . "x" .}}{{ $thumb := $image.Fit $size }}{{ $srcset = $srcset | append (printf ("%s %dw") $thumb.RelPermalink $thumb.Width ) }}{{- end }}
{{- end }}
{{ if .IsBlock }}  
<figure class="dib {{ .Attributes.class }}">
  <img loading=lazy importance=low src={{ $image.RelPermalink }}
       {{ if gt (len $srcset) 0 }} sizes={{site.Params.Images.sizes}} {{ $sl := delimit $srcset ", "}} srcset="{{$sl}}" {{ end }} width={{ $image.Width }}
	   height={{ $image.Height}} alt={{ $alt }} />
  {{ with $caption -}}<br /><figcaption class="mb3 i f6">» {{ $p.RenderString . }} «</figcaption>{{- end }}
</figure>
{{ else }}
<img src="{{ $image.RelPermalink }}" alt="{{ $alt }}" />
{{ end }}

and all of this is here in my collected samples

calles in the standard markdown way

![](images/qrcode.png "Sample QR Code")

You do not seem to be creating multiple image file types or switching format/image based on viewport, so you don’t need source or picture elements. You can just use an image tag with srcset= and sizes= attributes.

As @ju52 mentions, this will greatly reduce the amount of html you create.

Additionally, the source element is more of an instruction; you take away the ability of the browser to make sensible decisions about which image to use.

There’s also more than the speed of site build to consider. All of those created images have to be stored on disc somewhere.

I don’t think there is anything odd about having lots of sizes. There is a big difference to bridge from a retina ipad to a x2 old phone.
The trick for me is reusing sizes if possible for the retina versions.
E.g. my desktop size @1x is made to be the perfect size to also work on the most common android width of 1080px (360x3) once margins are factored in.

I generate multiple images, there’s nothing odd about that.

I don’t generate excessive html to do it, and I do not simply force them on users/devices.

I don’t assume that devices are all either one of only two possible pixel densities, because they aren’t. Browsers are pretty smart these days, and quite capable of deciding the best image to download, if you let them. Outside of Apple’s marketing, there is no such thing as a retina display.

I’ve never worked on a project where images are always displayed at the width of the device, but I guess others are different.

First of all, I would like to thank all the commentators. It is good to discuss with experts, even if you have different opinions. I took my first steps in computing on an IBM PC XT with 64 Kb Ram, 5.25" floppy, 10 MB hard disk and a 14" monochrome tube monitor with green writing. So I know what working with little memory means. But times have changed. Today, 1 GB SSD memory costs almost nothing - except at Apple :frowning:

@ju52
Thank you for providing the source code. The template is complex and compact. Can I see the result somewhere live on the internet? I understand that your stomach turns when you see, from your point of view, superfluous HTML code. But what are 500 bytes of HTML code compared to a 50 Kb saving on an image? I am not convinced by your suggestion to generally deliver the image with @1.5 or @2.0. Where is what saved then, 300 bytes of HTML code? I see it pragmatically. What counts is the result in the user’s browser. @1 images often look muddy on retina monitors. That’s why I want to provide these devices with a better resolution.

@nternetinspired

You do not seem to be creating multiple image file types or switching format/image based on viewport, so you don’t need source or picture elements. You can just use an image tag with srcset= and sizes= attributes.

You are right. I mainly use webp when the memory size is smaller than jpg or png. I also don’t change the format to square or focus the image. At the moment I don’t know if the image tag can handle media queries. I will check this out.

Additionally, the source element is more of an instruction; you take away the ability of the browser to make sensible decisions about which image to use.

I have given up on the decision-making and the hope that the browser will load the smallest picture. With the picture tag without media queries, all browsers load the first picture of the source tag. Even if it is the largest picture. This is also stated in the HTML5 specification. This is not just an assertion of mine, you can check it. Open the developer view in Google Chrome, select network, deactivate cache. Delete the loaded files and reload the browser. Repeat the whole thing in responsive mode. This way you can check exactly what the browser loads at which resolution and which viewport. With Hugo --bind, different operating systems in a network can access the development computer. In this way, you can also check the loading process of the browsers for @1 and @2 images.

There’s also more than the speed of site build to consider. All of those created images have to be stored on disc somewhere.

As already mentioned, SSD storage is very cheap nowadays. So is web space. Hardware costs are irrelevant for large companies. Here, only reliability, speed and fail-safety count.

@andrewd72

That’s exactly how I see it too. Provide each device with a manageable number of image sizes depending on resolution and viewport. And as I said, only one image is loaded and not 10.

I think you misunderstood me. You only serve one image format; you don’t need <picture> or <source> elements.

When you use <source> you force the browser to use the images you specified. Don’t do that, you are removing user choice.

I have given up on the decision-making and the hope that the browser will load the smallest picture.

No, you are making decisions about which image the browser downloads and forcing the browser to do that.

With the picture tag without media queries, all browsers load the first picture of the source tag.

Don’t use <picture> and <source> tags!

This is not just an assertion of mine, you can check it. Open the developer view in Google Chrome, select network, deactivate cache.

I understand how responsive images work, I’ve used them since ~2011. I am also familiar with dev tools.

Here, only reliability, speed and fail-safety count.

Bu you create more html than is needed, force users to download images they might not need, and serve images in a format that is not universally supported?

The max-width in sizes are effectively media queries, no?
You are serving the 480 pixel image up to max-width of 480px?
Do you take into account margins? For example why not serve the 480px image to a screen that is 520px wide if the image has a margin of 20px left and right.
An android phone is under the 480px in the query, so it gets the 480px image. In real pixels the screens are 360x3=1080.
So it really needs a bigger image than 480px (google pagespeed will tell you to serve higher quality images). Google pagespeed is ok with a x2 image (so 720px if it is full width, no margins).

Upps, i missed copying the changed config to my sample

using "sizes=(max-width: 30em) 90vw, (max-width: 60em) 80vw, 1480px" with the media sizes from tachions CCS

You do not know what you talk about and hasn’t understood the specification. A great many here use img srcset and I assure you it does choose the smallest picture with a resolution equal or higher to the viewport. If it does not, then you mess up the code, as it happened to me once (the sizes attribute).
Now, about pixel density, I don’t think worrying about makes any sense but what you need is this:

<img sizes="(min-width: 1200px),
            (max-width: 1199px),
            (max-width: 380px)"
     srcset="/img/blog/responsive-images-lg.png 1x,
             /img/blog/responsive-images-lg@2x.png 2x,
             /img/blog/responsive-images-md.png 1x,
             /img/blog/responsive-images-md@2x.png 2x,
             /img/blog/responsive-images-sm.png 1x,
             /img/blog/responsive-images-sm@2x.png 2x"
     src="/img/blog/reponsive-images.png"
     alt="responsive images">

Puh…, you also have to be able to see when you’ve got something wrong. In any case, I have learned a lot. This tip will then stand for how not to do it. Thanks to all for your expertise. And special thanks to @nternetinspired and @Tom_Durand.

1 Like

Every day in front-end world is a learning day. It is for me at least!

FWIW, there is a good use of picture and source tags, to serve alternative image formats. The browser will ignore what it doesn’t understand and skip to the next thing, so you can specify a preferred file type (e.g. .webp) in a source element, and then the fallback (e.g. .jpg) in the image tag, e.g:

<picture>
  <source 
    srcset="
      https://my-cdn/_1200x675/my-image.jpg.webp 1200w, 
      https://my-cdn/_992x558/my-image.jpg.webp 992w, 
      https://my-cdn/_768x576/my-image.jpg.webp 768w,
      https://my-cdn/_576x432/my-image.jpg.webp 576w" 
    sizes="100vw" 
    type="image/webp"
  >
  <img 
    alt="alt text should describe the visual content of the image…" 
    loading="lazy" 
    sizes="100vw" 
    src="https://my-cdn/_1200x675/my-image.jpg" 
    srcset="
      https://my-cdn/_1200x675/my-image.jpg 1200w, 
      https://my-cdn/_992x558/my-image.jpg 992w, 
      https://my-cdn/_768x576/my-image.jpg 768w, 
      https://my-cdn/_576x432/my-image.jpg 576w" 
    width="1200">
</picture>

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.