Center (proportional font) text in an HTML5 canvas
Asked Answered
A

8

31

I would like to be able to center single lines of text within rectangular areas I can calculate. The one thing I have expected to do in 2D geometry on a canvas is to center something whose width is unknown to you.

I have heard as a workaround that you can create the text in an HTML container and then call jQuery's width() function, but I ?didn't correctly handle the momentary addition to the document's body? and got a width of 0.

If I have a single line of text, significantly shorter than would fill most of the width in a screen, how can I tell how wide it will be on a canvas at a font size I know?

Antecede answered 7/12, 2012 at 21:34 Comment(1)
Ḋon't set display: none for element before reading its width.Pressroom
A
48

If you don't necessarily need a width of the text but just want to center text you can do

canvas_context.textBaseline = "middle";
canvas_context.textAlign = "center";

Which should put a text centered both vertically and horizontally.

See MDN's textAlign and textBaseline for extra info with pictures

Advertent answered 27/2, 2015 at 23:27 Comment(0)
L
60

You can do this by using measureText

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d")

canvas.width = 400;
canvas.height = 200;

ctx.fillStyle = "#003300";
ctx.font = '20px sans-serif';

var textString = "Hello look at me!!!",
    textWidth = ctx.measureText(textString ).width;


ctx.fillText(textString , (canvas.width/2) - (textWidth / 2), 100);

Live Demo

More elaborate demo

Lucic answered 8/12, 2012 at 1:25 Comment(5)
@ChristosHayward ...except that it isn't accepted anymore.Fakery
@Fakery well.. his comment was from 2012 ;)Lucic
@Lucic This feels like a time machine :DFakery
how did u get 100 as the center of the text?Nomanomad
100 is the heights center, canvas.height is set to 200 here, probably would have looked better if I made it, ctx.fillText(textString , (canvas.width/2) - (textWidth / 2), canvas.height/2);Lucic
A
48

If you don't necessarily need a width of the text but just want to center text you can do

canvas_context.textBaseline = "middle";
canvas_context.textAlign = "center";

Which should put a text centered both vertically and horizontally.

See MDN's textAlign and textBaseline for extra info with pictures

Advertent answered 27/2, 2015 at 23:27 Comment(0)
Y
25

developer.mozilla.org states in the textAlign description that the alignment is based on the x value of the context's fillText() method. That is, the property does not center the text in the canvas horizontally; it centers the text around the given x coordinate. Similar applies for the textBaseLine.

So in order to center the text in both directions, we need to set those two properties and position the text in the middle of the canvas.

ctx.textBaseline = 'middle'; 
ctx.textAlign = 'center'; 

ctx.fillText('Game over', canvas_width/2, canvas_height/2);
Yenta answered 12/10, 2015 at 13:40 Comment(2)
Good comment ! the API don't say that the ctx.textAlign = 'center' is centered from the x value of ctx.fillText('text', x, y) ... hours of research ...Photic
Extremely important nuance that is not documented well.Opuscule
M
3

For centering horizontally we use ctx.textBaseline = 'middle'; ctx.textAlign = 'center';. But to center vertically we need to use ctx.measureText.

(actualBoundingBoxAscent - actualBoundingBoxDescent) / 2 is important, because for different fonts text can be differently aligned vertically.

// https://mcmap.net/q/203429/-canvas-drawings-like-lines-are-blurry/59143499#59143499
function setCanvas(canvas, w, h) {
    canvas.style.width = w + 'px';
    canvas.style.height = h + 'px';
    const scale = window.devicePixelRatio;
    canvas.width = w * scale;
    canvas.height = h * scale;
    const ctx = canvas.getContext('2d');
    ctx.scale(scale, scale);
}

const width = 160;
const height = 160;
const fontSize = 100;
const text = 'A';

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

setCanvas(canvas, width, height); 

ctx.fillStyle = 'red';
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.fillStyle = 'white';
ctx.font = `${fontSize}px sans-serif`;
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
let { actualBoundingBoxAscent, actualBoundingBoxDescent } = ctx.measureText(text);
ctx.fillText(text, width / 2, height / 2 + (actualBoundingBoxAscent - actualBoundingBoxDescent) / 2);
canvas{
  outline: 1px black solid
 }
<canvas><canvas>
Molokai answered 9/6, 2022 at 11:2 Comment(0)
I
1

You can use ctx.textAlign = "center"; to align item to center

var canva = document.getElementById("canvas");
ctx.textAlign = "center"; // To Align Center
ctx.font = "40px Arial"; // To change font size and type
ctx.fillStyle = '#313439'; // To give font 
ctx.fillText("Text Center", 150 ,80);
Isola answered 14/8, 2020 at 15:26 Comment(1)
how did u determine "80" to be the center of the canvas?Nomanomad
S
0

For those wondering around how to properly align the height as well, since measureText() does not return the height (!).

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

const centered = (text, size) => {
  ctx.font = size + 'px monospace';
  let s = ctx.measureText(text).width;
  ctx.fillText(text, canvas.width / 2 - s / 2, canvas.height / 2 + size / 4);
}  
    
/* Just for the demo, really! 
 * Please don't do this!
 * Use proper animation frames requests
 * and clear timeout(s)!!! */
[["is", 40], ["able",45], ["<-- to -->",50], ["FLEX", 55], [" SMOOTH!", 58], ["FLEX!!", 62], ["FLEX!!!", 63] ].forEach((e) => {
  setTimeout(() => {
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    centered(e[0], e[1])
  }, e[1]*100)
})

// Intro test
centered("CANFLEX", 50)
canvas{
  outline: 1px black solid
 }
<canvas><canvas>
Sympathetic answered 29/3, 2022 at 22:56 Comment(2)
how did u determine "size" over 4 specifically?Nomanomad
For the How, likely trial error, and for the Why i can't tell precisely. However this might be a good separate question.Sympathetic
S
0

I use this to center/align both horizontal and vertical

function drawCanvas(text, font) {
    let canvas = document.querySelector("#canvas"),
        ctx = canvas.getContext("2d");
    canvas.width = 400;
    canvas.height = 280;
    ctx.fillStyle = "#000";
    ctx.font = font;
    let { width, actualBoundingBoxAscent, actualBoundingBoxDescent } = ctx.measureText(text),
        x_axis = (canvas.width / 2) - (width / 2),
        y_axis = canvas.height / 2 + (actualBoundingBoxAscent - actualBoundingBoxDescent) / 2;

    ctx.fillText(text, x_axis, y_axis);
}

let textList = ["40px Sans-serif", "20px Arial", "44px monospace", "18px Cambria", "32px Comic Sans MS"]
textList.forEach((text, i) => {
    setTimeout(function () {
        drawCanvas(text, text)
    }, i * 1500)
})
canvas{border: 1px solid green}
<canvas id="canvas"></canvas>
Saltwater answered 30/5 at 9:17 Comment(0)
S
-6

What you do is render your text off-screen using a negative text-indent, then grab the size.

text-indent: -9999px

See: hiding text using "text-indent"

Sublieutenant answered 7/12, 2012 at 21:39 Comment(3)
I don't understand how this has anything to do with the question being asked. He's looking to get the size so he can center them within canvas.Lucic
In think his answer is related on the workaround JonathanHayward mentioned, but asking the text's width directly to the canvas (as in your answer) is better (and more reliable) in this case.Napkin
Loktar, read the question "I didn't correctly handle the momentary addition to the document's body and got a width of 0." -- That's because you can't measure hidden content. The alternative is to make it visible but OFF SCREEN so the user doesn't see it, then measure it.Sublieutenant

© 2022 - 2024 — McMap. All rights reserved.