Hugo resize a picture on click

Updated: 2023-02-07
3 min read


Hugo by default uses parsing of markdown files. This means that we get the html code as it is written in markdown.

In order to understand which images we can enhance, we add a separate tag/key/id to those images


To implement the functionality, we need to:

  • Write/connect a script/handler that will perform the zoomin effect on the images we need
  • Add the necessary metadata to the images, so the script can find them

zoomin script

To add the ability to zoom on click, we will use the medium-zoom package.

This package provides this functionality in a non-loaded, handy style.


Script logic

The script finds images with id and so understands to apply the zoomin property to those images

Possible id:

  • zoom-default
  • zoom-margin
  • zoom-background
  • zoom-scrollOffset
  • zoom-trigger
  • zoom-detach
  • zoom-center

Connecting the scripts

In order for the script to work, we need to connect the logic as well as the handler.

Hugo has a static folder in the root of the project, which can be used to store static files (styles, scripts) and used to connect them to the site. If there is no such folder, you can create one.

In the static folder create a folder zoom-image and add two scripts to it


const zoomDefault = mediumZoom('#zoom-default')
const zoomMargin = mediumZoom('#zoom-margin', { margin: 48 })
const zoomBackground = mediumZoom('#zoom-background', { background: '#212530' })
const zoomScrollOffset = mediumZoom('#zoom-scrollOffset', {
    scrollOffset: 0,
    background: 'rgba(25, 18, 25, .9)',

// Trigger the zoom when the button is clicked
const zoomToTrigger = mediumZoom('#zoom-trigger')
const button = document.querySelector('#button-trigger')
button.addEventListener('click', () =>

// Detach the zoom after having been zoomed once
const zoomToDetach = mediumZoom('#zoom-detach')
zoomToDetach.on('closed', () => zoomToDetach.detach())

// Observe zooms to write the history
const observedZooms = [

// Log all interactions in the history
const history = document.querySelector('#history')

observedZooms.forEach(zoom => {
    zoom.on('open', event => {
        const time = new Date().toLocaleTimeString()
        history.innerHTML += `<li>Image "<em>${
            }</em>" was zoomed at ${time}</li>`

    zoom.on('detach', event => {
        const time = new Date().toLocaleTimeString()
        history.innerHTML += `<li>Image <em>"${
            }"</em> was detached at ${time}</li>`


// Show placeholders for paragraphs
const paragraphs = []'p.placeholder'))

paragraphs.forEach(paragraph => {
  // eslint-disable-next-line no-param-reassign
  paragraph.innerHTML = paragraph.textContent
    .split(' ')
    .filter(text => text.length > 4)
    .map(text => `<span class="placeholder__word">${text}</span>`)
    .join(' ')

CDN script

You can download the script, or you can upload it

Script Link

Adding to template

In order for these scripts to work in the website template, they must be connected.

I use for this the template baseof.html. I simply add links to the scripts in body of the template.

    # baseof.html


    <script src="" defer></script>
    <script src="/js/zoom-image/placeholders.js" defer></script>
    <script src="/js/zoom-image/index.js" defer></script>


image ID

Hugo allows you to change the parsing behavior of markdown files with hooks. You can read more about render-hooks at website.

In the *layouts folder.

Let’s add the file render-image.html to the following path layouts -> _default -> _markup

file code:

<p class="md__image">
    <img src="{{ .Destination | safeURL }}" id="zoom-default" alt="{{ .Text }}" {{ with .Title}} title="{{ . }}" {{ end }} />

We only added id="zoom-default" to the default code