For the image, I add in a new promise method called loadImage which takes the url of an image, loads it, and then returns resolution on completion.

loadImage(url) {

return new Promise((resolve, revoke) => {

let img = new Image() img.crossOrigin = 'Anonymous' img.onload = () => {

resolve(img)

} img.src = url

})

}

Using custom fonts in canvas can be very tricky but Bram Stein wrote the excellent Font Face Observer loader to help recognize when custom typography was loaded. Back in our CSS, we may have a @font-face declaration which looks something like this.

@font-face{

font-family: 'Runes';

src: url('/runes.woff2') format('woff2'),

url('/runes.woff') format('woff');

font-weight: normal;

font-style: normal;

}

Back in our component, we can then use Font Face Observer to check to see if that typeface has loaded.

let font = new FontFaceObserver('Runes') font.load().then(() => {

console.log('loaded')

})

As you can see from this snippet, Font Face Observer also returns a promise so we can actually combine both our image and typography loading together to be confident that all assets are ready before called canvas.

let font = new FontFaceObserver('Runes') Promise.all([

loadImage('/background.jpg'),

font.load()

]).then(data => {

let background = data[0]

})

In order to generate the names, I just use a few canvas methods to place the text exactly where it needs to go. I’ll reference my design in Figma to find the exact size and positioning.

context.textAlign = 'center'

context.textBaseline = 'middle'

context.font = '24px Runes'

context.fillText('Lee', 540, 540)

In the case of this project, the text was generated on a separate canvas and then placed onto another canvas which included the background image. This allowed the names to be generated in a very exact manner but then resized and placed onto varied background images. You can use the drawImage method to place both the preloaded background and dynamic names onto a new canvas.

context.drawImage(background, 0, 0)

context.drawImage(names, 135, 135, 810, 810)

The last thing you’ll want to do is provide your user with download instructions which fit their current environment. On a mobile device, the user can’t click to download an image so the instructions should read: “Press and hold to download image.” (I use mobile-detect to see if the user is coming from a mobile device.) On the desktop, I’ll wire up one-click downloading with downloadjs to provide the best experience. You can actually pass in the same computed method to power this function.