An Image Slideshow Shortcode For Hugo

Creating static web sites with Hugo is fun and fast but providing a convenient shortcode to smoothly cross-fade an unknown number of images in a blogpost gets a bit tricky… So let’s go!

Shortcodes are Hugo template snippets which can be used inside a markdown document with optional named or unamed parameters.

The snippet is for example called slide.html and has to be placed in a folder called shortcodes in the layout directory of the Hugo site.

To slide some images we need to know the folder’s location. So the shortcode in the markdown document will look like this:

{{< slide "/assets/img/">}}

The shortcode itself starts with reading out the parameter and 2 constants defining the fade-in time and the number of seconds for how long an image is visible. The localFolder may require some string manipulations to match the local directory.

Read the folder, count the number of files in it and calculate the duration of the whole css animation.

{{ $fadein := 2 }}
{{ $visible := 4 }}
{{ $param := .Get 0 }}
{{ $localFolder := printf "/static%s/" $param }}

{{ $files := sort (readDir $localFolder) }}
{{ $numberOfFiles := len $files }}
{{ $animationDuration := mul (add $fadein $visible) $numberOfFiles }}

Now create the slider div, iterate through the files and generate an img tag for each file.

 <style>
    .slider {
        padding-bottom: 70%;
        width: 100%;
        height: 0;
        position: relative;
    }    
    .slider img {
        width: 100%;
        height: auto;
        position: absolute;
        opacity: 0;
        animation: slide infinite {{$animationDuration}}s;
    }

    {{ $x := div 100.0 $animationDuration }}
    {{ $p0 := 0 }}
    {{ $p1 := mul $x $fadein }}
    {{ $p2 := mul $x (add $fadein $visible) }}
    {{ $p3 := mul $x (add (add $fadein $visible) $fadein) }}

    @keyframes slide {
        {{ $p0 }}% { opacity: 0; }
        {{ $p1 }}% { opacity: 1; }
        {{ $p2 }}% { opacity: 1; }
        {{ $p3 }}% { opacity: 0; }
    }

    {{ range $index, $value := $files }}
    {{ $delay := mul (add $fadein $visible) $index }}
    .slider img:nth-child({{add $index 1}}){animation-delay:{{$delay}}s;}
    {{ end }}
</style>

The last part is the css animation.

All images will get an opacity of 0 and an infinite animation with a keyframe rule binding.

Before we define the keyframe rule we calculate some percentages for it. If you are not familiar with these keyframe calculations the code may not explain everything but at least may give you a hint on how the percentages are calculated.

The individual animation delay for every image is the last step to fade one image into the next one.

 
    .slider {
        padding-bottom: 70%;
        width: 100%;
        height: 0;
        position: relative;
    }    
    .slider img {
        width: 100%;
        height: auto;
        position: absolute;
        opacity: 0;
        animation: slide infinite {{$animationDuration}}s;
    }

    {{ $x := div 100.0 $animationDuration }}
    {{ $p0 := 0 }}
    {{ $p1 := mul $x $fadein }}
    {{ $p2 := mul $x (add $fadein $visible) }}
    {{ $p3 := mul $x (add (add $fadein $visible) $fadein) }}

    @keyframes slide {
        {{ $p0 }}% { opacity: 0; }
        {{ $p1 }}% { opacity: 1; }
        {{ $p2 }}% { opacity: 1; }
        {{ $p3 }}% { opacity: 0; }
    }

    {{ range $index, $value := $files }}
    {{ $delay := mul (add $fadein $visible) $index }}
    .slider img:nth-child({{add $index 1}}){animation-delay:{{$delay}}s;}
    {{ end }}

The following shortcode in action has an additional JavaScript navigation and some prefixed css properties to support a wider range of browsers.