Using #wide with Markdown images and Hugo processing

Okay, let me see if I can explain this clearly.

I had a guy build me a basic Hugo theme because I’m bad at design. One of the cool features of his theme is that it uses a little CSS wizardry to make full-width images using the markdown image tag by appending #wide to the image URL:

![An image of my cat that's full-width](/images/my-cat.jpg#wide)

This is accomplished in his CSS like this:

  img[src$='#wide'] {
    display: block;
    width: 100vw;
    max-width: none;
    margin-left: 50%;
    transform: translateX(-50%);
    border-radius: 0;
  }

Here’s the thing, though: I want to use Hugo’s built in image processing features. But Hugo gets confused if I have #wide in the end of file names.

So to solve this problem, I have to hook in to the markdown engine via a file in /layouts/_default/_markup/

So the first thing I do is add the file render-image.html into that folder, with this content:

{{ $src := (resources.Get .Destination)  }}
{{ $alt := .PlainText | safeHTML }}

{{ if $src }}

  {{ $tinyw := default "500x webp" }}
  {{ $smallw := default "800x webp" }}
  {{ $mediumw := default "1200x webp" }}
  {{ $largew := default "1500x webp" }}

  {{ $data := newScratch }}
  {{ $data.Set "tiny" ($src.Resize $tinyw) }}
  {{ $data.Set "small" ($src.Resize $smallw) }}
  {{ $data.Set "medium" ($src.Resize $mediumw) }}
  {{ $data.Set "large" ($src.Resize $largew) }}

  {{ $tiny := $data.Get "tiny" }}
  {{ $small := $data.Get "small" }}
  {{ $medium := $data.Get "medium" }}
  {{ $large := $data.Get "large" }}

  <picture>
    <source media="(max-width: 376px)" 
      srcset="{{with $tiny.RelPermalink }}{{.}}{{ end }}">
    <source media="(max-width: 992px)" 
      srcset="{{with $small.RelPermalink }}{{.}}{{ end }}">
    <source media="(max-width: 1400px)" 
      srcset="{{with $medium.RelPermalink }}{{.}}{{ end }}">
    <source media="(min-width: 1600px)" 
      srcset="{{with $large.RelPermalink }}{{.}}{{ end }}">
    <img alt="{{ $alt }}" loading="lazy" title="{{ $alt }}" 
      src="{{ $src }}" height="{{ $src.Height}}" width="{{ $src.Width }}">
  </picture>
{{ else }}
<code>ERR: IMAGE RESOUCE FAILED TO MOUNT TO HUGO</code>
{{ end }}

I forget where I got this code (maybe Hugo docs!), but it is really great because it takes the image as a resource, resizes it a few times, converts to webp, and outputs the tag as a <picture> tag instead of an <img> tag. Very nifty.

The problem with this, though, is that I can’t take advantage of my guy’s super cool CSS trick for full-size images in markdown! If I have #wide on my images Hugo won’t be able to find them. It will look for a file that is literally called /images/somefile.jpg#wide.

So to fix the problem I use in and substr to look for and remove #wide, and then readd it later with printf.

So first I make a $destination variable to handle that search and remove of the #wide parameter:

{{ $destination := "" }}

Next, I search for #wide using in and then, if present, remove it with substr:

{{ if in .Destination "#wide" }}
  {{ $destination = (substr .Destination 0 -5) }}
{{ else }}
  {{ $destination = .Destination }}
{{ end }}

Then, I use my newly made $destination variable in place of .Destination in the original code:

{{ $src := (resources.Get $destination)  }}

The rest of the code continues as normal until I need to output the image. At that point, I just re-run my if conditional to check for #wide again, and if it’s present, I append with printf:

    <img alt="{{ $alt }}" loading="lazy" title="{{ $alt }}" 
      src="
        {{- if in .Destination "#wide" -}}
          {{- printf "%s#wide" $src -}}
        {{- else -}}
          {{- $src -}}
        {{- end -}}" height="{{- $src.Height -}}" width="{{- $src.Width -}}">

It works like a charm!

The end result is I can still use markdown image commands, and even put #wide on the end of the filename and everything works like it should, except now it also takes advantage of Hugo’s image processing!

Let me know what you think, or if you know a cleaner way to achieve this!

3 Likes

I’m glad you hacked your way to a solution that works for you!

As for as a better approach, I imagine it would be to make the CSS rule as a class selector and change the way you “encode” what you want with the URL to use URL query instead of an anchor tag.

Something like:

![An image of my cat that's full-width](/images/my-cat.jpg?c=wide)

And then in your render-image.html, you parse the url with the Hugo url parsing tools and you get the clean source path, and you have a flag now you can use to add that specific class to your img tag if you had included it in the url. That seems the cleaner way to accomplish it. @jmooring has a great approach he’s created using the render hook and the url query arguments. Is it possible to use attribute lists with render hooks? - #9 by jmooring

The approach you describe is fine for your situation, but it’s brittle, that’s all.

6 Likes

How would you write: “that indicated file + whatever additional suffix” ?
Obviously <a href="{{ .Destination | safeURL }}.big"> doesn’t work because it means selector “big”
I just can’t learn the fine details of Go template, the syntax is awful, awful. Especially when you come Pascal or Ada…

That way, when people click they’ll see the full pictures, without the browser having to take 10 secs to upload the page…

I’m not sure what you are trying to communicate. Your code example doesn’t make any sense even as basic HTML.

If you want a picture to link to the original size of that picture, then you link to the original picture in the markdown, as a link, not an image.

Otherwise, there are CSS and Javascript ways to zoom or expand images on the page.

1 Like

/images/my-cat.jpg#wide

The theme Papermod is using a similar technique to add custom CSS to images

I prefer the Markdown Attributes, recently added in Hugo (0.87). The doc is incomplete, see this post for details

That article (https://roneo.org/en/hugo-custom-css-classes-images-markdown-attributes/) is incorrect.

The examples provided will add attributes to the wrapping paragraph, not the image.

Markdown attributes are applicable to block elements, not inline elements.

2 Likes

Thanks for the clarification jmooring!

I probably don’t get all the implications though:
I’m only using this feature for a few weeks (and only for pictures) but I didn’t notice any limitation.
Any potential drawback I should keep an eye on?

(An example was added to the post to clarify the outcome and ease the discussion)

The image (inline) in your example is enclosed in a p element (block) which seems to happen because attributes in MD can only be applied to blocks.

That is not necessarily what people want, since it adds an element to the DOM that is not needed. Basically, the style you provide in your example is not applied to the image but to the paragraph containing it.

So what you describe in your blog kind of does what you say, but it is not about styling an image per se: you’re introducing a new element and are mostly styling that.

1 Like

sorry yeah I was a bit in a hurry writing that.
So: I use a script to process all the pictures, making them lighter. That’s the easiest way to me, and a transparent one.
But instead of just replacing the images, I thought I could have a “.big” version which would simply be the old one, that people could display upon clicking on the picture.
So an automatic link generated by render-image.html would do the trick fine.

Here’s the complete render-image.html:

{{- if .Page.Site.Params.BookPortableLinks -}}
  {{- template "portable-image" . -}}
{{- else -}}

<figure>
   <figcaption><p>{{ with .Title }} {{ . | markdownify }} {{ end }}
   </p></figcaption>
  <img href="{{ .Destination | safeURL }}" src="{{ .Destination | safeURL }}" alt="{{ .Text }}" {{ with .Title }}title="{{ . }}"{{ end }}/>
</figure>

  {{- end -}}

{{- define "portable-image" -}}
  {{- $isRemote := or (in .Destination "://") (strings.HasPrefix .Destination "//") }}
  {{- if not $isRemote }}
    {{- $path := print .Page.File.Dir .Destination }}
    {{- if strings.HasPrefix .Destination "/" }}
      {{- $path = print "/static" .Destination }}
    {{- end }}
    {{- if not (fileExists $path) }}
      {{- warnf "Image '%s' not found in '%s'" .Destination .Page.File }}
    {{- end }}
  {{- end }}
<figure>
   <figcaption><p>{{ with .Title }} {{ . | markdownify }} {{ end }}
   </p></figcaption>
  <img href="{{ .Destination | safeURL }}" src="{{ .Destination | safeURL }}" alt="{{ .Text }}" {{ with .Title }}title="{{ . }}"{{ end }}/>
  </figure>
{{- end -}}

So instead of the .Destination in the link, I would like to automatically add .big extension

That code example doesn’t make any sense to me…

but if in your markdown you do a typical markdown image, you could either wrap that image in a markdown link, or in your render-image.html, you could have it render normally but wrap it with a link tag <a href=".Destination"><img src=".Destination"></a> and for the first .Destination you can modify that string however you want. {{ replace .Destination ".jpg" "-big.jpg" }}

Then the image is showing the smaller image and the link of the image will take you to the original size. The locations of the images have to be in the same place and no hugo image processing.

1 Like

The code I posted rendered weirdly (I forgot to escape certain characters) so it didn’t help. But it DOES work.

Second,

Is exactly what I wanted. I really am no programmer…
This:

 <a href="{{ .Destination | safeURL }}">
<img src="{{ replace .Destination ".jpg" ".jpg.small" | safeURL }}"
alt="{{ .Text }}"
{{ with .Title }}title="{{ . }}"{{ end }}/>
</a>
</figure>

worked GREAT, thanks !

1 Like