Search code examples
javascripthtmlcanvashtml5-canvas

Canvas context.measureText not working properly


I am trying to add background to a sentence and draw it on canvas.

On click on animate button, the text-background (red) drawn improperly on first word. When consoled measuretext() on that word, the value is way lesser.

Here is the function which animates and add text background.

const fillMixedText = (canv, ctx, args, x, y) => {
  let defaultFillStyle = "black";
  let defaultFont = "600 54px Arial";
  ctx.save();
  let i = 0;
  args.forEach(({ text, fillStyle, font }) => {


    // console.log("x",x);
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.textBaseline = "top";

    ctx.fillStyle = "red";
      console.log(x,text,ctx.measureText(text).width)
      ctx.fillRect(x, y, ctx.measureText(text).width , 70);

    // console.log(text, ctx.measureText(text).width);
    ctx.fillStyle = fillStyle || defaultFillStyle;

    ctx.font = defaultFont;
    ctx.fillText(text, x, y);
    x += ctx.measureText(text).width;
    i++;
  });
  ctx.restore();
};

This is the JSFiddle

I want the whole sentence to get a background.


Solution

  • measureText will use the currently set font property, and is also relative to the current context's transform (well, its scale at least).

    So you must use it with the actual settings you'll be drawing your text with to get a correct measure:

    var canvas = document.getElementById("canvas-1");
    var ctx = canvas.getContext("2d");
    
    var args = [
      {text: "constantinople "},
      {text: "is "},
      {text: "a "},
      {text: "city. "}
    ];
    
    
    document.getElementById('click').onclick = function() {
      var startTime = new Date().getTime();
      var interval = setInterval(function() {
        if (new Date().getTime() - startTime > 2000) {
          clearInterval(interval);
        }
        ctx.clearRect(0, 0, canvas.width, canvas.width);
    
        animateText();
      }, 33);
    
    }
    
    var interval;
    let distance = 0;
    let speed = 15;
    
    function animateText() {
      // interval = setInterval(function() {
      distance = distance + speed;
      textAnimation(
        ctx,
        canvas,
        args, -200,
        canvas.height / 2,
        distance
      ); // console.log(thiscanvas.width/16)
    }
    
    textAnimation = (ctx, canv, args, x, y, distance) => {
      if (distance >= 280) {
        distance = 0;
        // clearInterval(interval);
        x = canv.width / 16;
      }
    
      fillMixedText(canv, ctx, args, x + distance, y);
    };
    
    const fillMixedText = (canv, ctx, args, x, y) => {
      let defaultFillStyle = "black";
      let defaultFont = "600 54px Arial";
      ctx.save();
      let i = 0;
      args.forEach(({ text, fillStyle, font }) => {
    
        // console.log("x",x);
        ctx.setTransform(1, 0, 0, 1, 0, 0);
        ctx.textBaseline = "top";
    
        ctx.fillStyle = "red";
        // set the font first
        ctx.font = defaultFont;
        // now it's correct
        ctx.fillRect(x, y, ctx.measureText(text).width, 70);
    
        ctx.fillStyle = fillStyle || defaultFillStyle;
        ctx.fillText(text, x, y);
    
        x += ctx.measureText(text).width;
        i++;
      });
      ctx.restore();
    };
    canvas {
      border: 2px solid black;
      width: 700px;
      height: 400px;
      margin-left: 30px;
      margin-top: 10px;
    }
    <canvas id="canvas-1" width="1280px" height="720px"></canvas>
    <button id="click">animate</button>