This post walks through how to add an HTML Canvas to a Hugo site, with additional guidance on including external JavaScript libraries from a CDN, such as Three.js to render the cube shown below:

Hugo Shortcodes

Adding support to a new HTML DOM in your Hugo site is possible using Hugo shortcodes. Which is a way to add custom HTML elements to your website like image, video, and HTML canvas. The shortcode is a HTML template invoked within markup, accepting any number of arguments.

To create a new shortcode, just create a new file at layouts/_shortcodes/myshortcode.html. Now you can invoke that shortcode from your markdown using the following syntax:

1
{ {<myshortcode id=test1 src="scripts/test1.js">}}

Hugo will replace that call with the content of your shortcode.

Sending data from markdown to HTML

From the markdown, you can pass multiple params to the shortcode. Each param can be string, integer, floating point, or boolean.

For example

1
{ {<myshortcode flag=false text="my text" width=399 >}}

Built-in Shortcodes

Note that Hugo comes with some built-in shortcodes like that are ready to be used.

1
2
3
{ {< figure src=/images/bulding.jpg >}}

{ {< figure src=/images/red-bulding.jpg alt="A red building" >}}

But we don’t have one for canvas yet, so we need to create our own.

Canvas Shortcode

Create a new shortcode called canvasjs and add the following content:

1
2
3
4
5
6
7
8
9
{{ $id := .Get "id" }}
{{ $src := .Get "src" }}

<div style="display: flex; justify-content: center; align-items: center;">
    <div style="width: 500px; height: 500px;" id="div-container-{{$id}}">
    </div>
</div>

<script type="module" src="{{$src}}"></script>

Now we can invoke the canvas from the markdown using the following syntax:

1
{ {<canvasjs id=test1 src="scripts/test1.js">}}

We also need to create the script test1.js. To do that, create a new scripts/test1.js file at same level as your markdown file with the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const parent = document.getElementById('div-container-test1');
const canvas = document.createElement('canvas');
canvas.width = parent.clientWidth;
canvas.height = parent.clientHeight;
parent.appendChild(canvas);
const ctx = canvas.getContext('2d');
const rectWidth = 100;
const rectHeight = 100;
const rectX = (canvas.width - rectWidth) / 2;
const rectY = (canvas.height - rectHeight) / 2;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#EEE';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'red';
ctx.fillRect(rectX, rectY, rectWidth, rectHeight);

Resulting in:

And we are done.

Three.js & Importmaps

Libraries like Three.js goes well with HTML canvas. To use such library, we can create a new shortcode that creates a importmap which allow you to use the import module syntax inside your script.

Create a new shortcode at layouts/_shortcodes/importmap.html with the following content:

1
2
3
4
5
6
7
8
9
{{ $version := .Get "version" }}

<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/three@{{$version}}/build/three.module.js"
    }
  }
</script>

The importmap must be the first <script> parsed by the browser. So the invokation of the shortcode should be the first thing in your markdown file. Just invoke it like we did with the canvasjs passing the library version as parameter:

1
{ {<importmap version="0.160.0">}}

Now just call the same canvas shortcode we created earlier

1
{ {<canvasjs id="sphere1" src="scripts/sphere1.js">}}

Inside the sphere1.js we can just import three like a module:

1
2
3
import * as THREE from 'three';

// code to render a sphere using threejs

Resulting: