HTML5 Canvas API Tutorial Part 4 - Drawing Text
Greetings, friends! Welcome to Part 4 of my HTML5 Canvas API tutorial. In this tutorial, I will discuss how to draw text to an HTML5 canvas.
Canvas Setup
Create a file called index.html
with the following contents:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas API Tutorial</title>
</head>
<body>
<h1>Canvas API Tutorial</h1>
<canvas id="canvas" width="500" height="500" style="border: 1px solid black;"></canvas>
<script src="canvas.js"></script>
</body>
</html>
Create a script called canvas.js
with the following contents in the same directory:
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
Drawing Text on the Canvas
To draw text to the canvas, we will utilize these two methods frequently: ctx.fillText and ctx.strokeText. Each method takes four arguments:
- text: A DOMString specifying the text string to render into the context. The text is rendered using the settings specified by font, textAlign, textBaseline, and direction.
- x: The x-axis coordinate of the point at which to begin drawing the text, in pixels.
- y: The y-axis coordinate of the point at which to begin drawing the text, in pixels.
- maxWidth (optional): The maximum number of pixels wide the text may be once rendered.
Let's try it out! We will add the following line to the canvas.js
file:
ctx.fillText('Hello world', 200, 200)
This will draw the text, "Hello World", to the canvas at the position, (200, 200) where the bottom-left corner of the beginning of text is roughly at (200, 200).
The "font" Property
You will notice immediately that the font seems a bit too small. The default font is 10px, sans-serif. Let's change the font size by setting the ctx.font property:
ctx.font = '48px serif'
ctx.fillText('Hello world', 200, 200)
Much better! By changing the font property on the canvas context before we call ctx.fillText
or ctx.strokeText
, we can change certain properties of the font such as font size, font weight, and font family.
When setting the value of the ctx.font
property, we pass in a string that is parsed as a CSS font value. It's very similar to setting the font
property in a CSS file. For example, let's set the font-weight to bold and font-family to cursive:
ctx.font = 'bold 48px cursive'
ctx.fillText('Hello world', 200, 200)
If you look at the ctx.font MDN article, you may see it mention something known as a DOMString. The MDN article associated with a DOMString might sound confusing, but you shouldn't get caught up in the details. A normal JavaScript string is automatically a DOMString
instance. Therefore, the ctx.font
property simply accepts a normal string that is then parsed as a CSS font property. DOMStrings are kept around for legacy reasons to ensure interoperability between certain Web APIs.
The "textAlign" Property
You can align text horizontally using the ctx.textAlign property. You can choose between the following: "left", "right", "center", "start", "end".
Let's look at an example of each type of text alignment. We'll replace the contents of canvas.js
with the following:
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
const canvasCenter = canvas.width / 2
ctx.beginPath()
ctx.moveTo(canvasCenter, 0)
ctx.lineTo(canvasCenter, canvas.height)
ctx.stroke()
ctx.font = '30px serif'
ctx.textAlign = 'left'
ctx.fillText('left-aligned', canvasCenter, 40)
ctx.textAlign = 'center'
ctx.fillText('center-aligned', canvasCenter, 85)
ctx.textAlign = 'right'
ctx.fillText('right-aligned', canvasCenter, 130)
ctx.textAlign = 'start'
ctx.fillText('start-aligned-ltr', canvasCenter, 175)
ctx.textAlign = 'end'
ctx.fillText('end-aligned-ltr', canvasCenter, 220)
ctx.direction = 'rtl'
ctx.textAlign = 'start'
ctx.fillText('start-aligned-rtl!', canvasCenter, 265)
ctx.textAlign = 'end'
ctx.fillText('end-aligned-rtl!', canvasCenter, 310)
This will let us see how the text looks under different alignments. If you choose not set the ctx.textAlign
property, it will be set to "start" by default.
When the text is going from right to left (rtl), certain characters will appear at the beginning of the text. Some languages such as Persian read from right to left and therefore the punctuation (such as an exclamation mark or question mark) is placed at the far-left of the text.
The "textBaseline" Property
Sometimes, you need to adjust the text baseline depending on the language you're using or how you want to align text with other elements on the canvas. This can be done through the ctx.textBaseline property. You can set this property to one of the following values: "top", "hanging", "middle", "alphabetic", "ideographic", "bottom".
The following image from the WHATWG website demonstrates different alignment points in the font. Each alignment point corresponds to a value that can be set on the ctx.textBaseline
property.
Let's see an example of each baseline attribute, taken directly from MDN's website:
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
const baselines = ['top', 'hanging', 'middle', 'alphabetic', 'ideographic', 'bottom']
ctx.font = '36px serif'
ctx.strokeStyle = 'red'
baselines.forEach((baseline, index) => {
ctx.textBaseline = baseline
const y = 75 + index * 75
ctx.beginPath()
ctx.moveTo(0, y + 0.5)
ctx.lineTo(550, y + 0.5)
ctx.stroke()
ctx.fillText(`Abcdefghijklmnop (${baseline})`, 0, y)
})
This will result in six lines that are equidistant from each other. Notice how the text is rendered differently depending on the baseline.
Gathering Text Metrics
You may want to gather certain dimensions of the text rendered to the canvas such as the width, measured in CSS pixels. We can retrieve the width of the text from the TextMetrics
object we get back from the ctx.measureText
method. Things like the font-size, font-weight, and font-family can affect the value of the width.
Let's look at an example. Replace all the contents of canvas.js
with the following:
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
const smallText = ctx.measureText('Hello world')
console.log(smallText.width)
ctx.fillText('Hello world', 100, 100)
ctx.font = '48px serif'
const bigText = ctx.measureText('Hello world')
console.log(bigText.width)
ctx.fillText('Hello world', 100, 200)
The small text is 10px, sans-serif by default. When I measure the width using Google Chrome, I get a width of 49.462890625 pixels for the smallText
and a width of 230.625 pixels for the bigText
. These values may change depending on your browser, device, font size, font weight, font family, and other properties that affect the font.
You might be wondering why we should care about the width of the text if it changes depending on the browser and device. Wouldn't that make it hard to develop applications that work across different browsers then? When developing applications, we can use the width of the text and compare it against something such as the size of a rectangle on the canvas or the entire canvas width. By looking at the text width as it relates to other objects, we can make it more useful.
Rendering text inside a canvas can be difficult. You will commonly see developers use normal HTML elements for rendering text alongside the HTML canvas. Sometimes, you need to use tools the Canvas API provides for manipulating text. Choose the tool that makes more sense for the application you're trying to build.
Conclusion
The HTML5 Canvas API provides two methods for rendering text to the canvas: ctx.fillText and ctx.strokeText. There are four main properties you can adjust for styling text: ctx.font, ctx.textAlign, and ctx.textBaseline. You can control the ctx.font
property like the CSS font property. Additionally, you can use the ctx.measureText method to retrieve metadata about the text you want to render to the screen. I should note that there are some experimental APIs that add more text rendering features such as ctx.direction, but they aren't supported in every browser yet.
In the next tutorial, I'll dive into how to insert images into the canvas. Then, we can move onto pixel manipulation using the Canvas API! Exciting times ahead so don't miss it! 😃