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?

1
  • Ḋon't set display: none for element before reading its width.
    – nrodic
    Commented Dec 7, 2012 at 21:39

8 Answers 8

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

5
  • @ChristosHayward ...except that it isn't accepted anymore. Commented Jul 9, 2019 at 15:29
  • @DavidsKanal well.. his comment was from 2012 ;)
    – Loktar
    Commented Jul 9, 2019 at 15:54
  • 2
    @Loktar This feels like a time machine :D Commented Jul 9, 2019 at 19:05
  • how did u get 100 as the center of the text? Commented Jun 20, 2022 at 5:14
  • 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);
    – Loktar
    Commented Jun 23, 2022 at 18:06
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

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);
2
  • 1
    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 ... Commented May 1, 2020 at 2:20
  • Extremely important nuance that is not documented well. Commented Jan 31 at 18:13
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://stackoverflow.com/questions/8696631/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>

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);
1
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>

2
  • how did u determine "size" over 4 specifically? Commented Jun 20, 2022 at 5:13
  • For the How, likely trial error, and for the Why i can't tell precisely. However this might be a good separate question.
    – NVRM
    Commented Jun 20, 2022 at 10:11
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>

-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"

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.
    – Loktar
    Commented Dec 8, 2012 at 1:17
  • 1
    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.
    – Roimer
    Commented Dec 8, 2012 at 3:04
  • 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. Commented Dec 11, 2012 at 17:13

Not the answer you're looking for? Browse other questions tagged or ask your own question.