Search code examples
javascriptcanvashtml5-canvas

Put text below another text, and align it to the right


Let's take a look at my canvas code here (in canvas lib but almost same as html5 canvas API)

const canvas = createCanvas(600, 316);
const ctx = canvas.getContext('2d');

const codeName = 'Some String Here';

const image = await loadImage(...);

ctx.drawImage(image, 0, 0);

ctx.font = 'bold 30px Sans serif';
ctx.fillStyle = '#d75c5c';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const titleX = canvas.width / 2;
const titleY = canvas.height / 2;
ctx.fillText(codeName, titleX, titleY);

This code results in an image like this: current result

Now, how do I add another text, just below the 'Some String Here', such that it aligns to the right, without exceeding the width of the 'Some String Here' text? This is what I want it to look like: expected result

It'd also be nice if the bottom text went became multi-lined if it exceed more than 70% of the 'Some String Here' text.

Here's what I've tried:

const authorText = `Bottom String`
ctx.font = '20px Sans serif';
ctx.fillStyle = '#494949';
ctx.textAlign = 'right';
ctx.textBaseline = 'top';

const titleTextWidth = ctx.measureText(codeName).width;
const titleTextHeight = ctx.measureText(codeName).actualBoundingBoxAscent;
const spacing = 15;

const authorX = titleX + titleTextWidth;
const authorY = titleY + titleTextHeight + spacing;
ctx.fillText(authorText, authorX, authorY);

However, this has some weird quirks where the bottom text will pop out a bit to the right: enter image description here


Solution

  • Your code has two issues:

    • You should call ctx.measureText before you change the text-related properties to draw the second text.

    • When calculating authorX using titleX, you need to keep in mind titleX doesn't correspond to the distance to the left side of the text's bounding box, but to the text's alignment point (the text center, in this case, as it's using textAlign = 'center').

      That means that text's bounding box has left = titleX - titleTextWidth / 2 and right = titleX + titleTextWidth / 2 (the second one is your authorX).

    Here's a working version of your code:

    const canvas = document.getElementById('canvas')
    
    canvas.width = document.body.offsetWidth;
    canvas.height = document.body.offsetHeight;
    
    const ctx = canvas.getContext('2d');
    
    const codeName = 'Some String Here';
    ctx.font = 'bold 30px Sans serif';
    ctx.fillStyle = '#d75c5c';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    
    const titleX = canvas.width / 2;
    const titleY = canvas.height / 2;
    
    ctx.fillText(codeName, titleX, titleY);
    
    // Measure before changing the text properties:
    const titleTextWidth = ctx.measureText(codeName).width;
    const titleTextHeight = ctx.measureText(codeName).actualBoundingBoxAscent;
    
    const authorText = 'Bottom String'
    ctx.font = '20px Sans serif';
    ctx.fillStyle = '#494949';
    ctx.textAlign = 'right';
    ctx.textBaseline = 'top';
    
    const spacing = 15;
    
    // Properly calculated x:
    const authorX = titleX + titleTextWidth / 2;
    const authorY = titleY + titleTextHeight + spacing;
    
    ctx.fillText(authorText, authorX, authorY);
    html,
    body {
      margin: 0;
      height:100%;
    }
    
    #canvas {
      position:absolute;
      width: 100%;
      height :100%;
    }
    <canvas id="canvas"></canvas>