Search code examples
javascriptcanvastextrendering

JS Canvas: Why is the text rendering not smooth?


why is the text not rendered smoothly in the following JavaScript canvas example?

let canvas = document.getElementById('canvas');
let context = this.canvas.getContext('2d');
let tick = 0;

let loop = function() {
  context.clearRect(0, 0, canvas.width, canvas.height);
    
  let scale = 1 + tick * 0.005;
  context.save();
  context.scale(scale,scale)
    
  context.font = 'bold 50px Sans-serif';
  context.textBaseline = 'top';
  context.fillStyle = 'black';
  context.fillText('Hello, world!', 0, 0);
  
  
  context.restore();
  tick++;  
  window.requestAnimationFrame(loop);
}

window.requestAnimationFrame(loop);

Demo: https://jsfiddle.net/kemrf9p7/

The y position is always exactly the same, despite not being changed by the code. It seems to be y +/- 1.

I have tested different ways to sclae text without this strange behavior but couldn't find a solution. For example I did not use scale() but changed the size of the text directly. I also tried different values for textBaseline.


Solution

  • Here is a try to fix the problem. First of all you may draw a canvas on other canvas, so I tried to create a 5000*5000px canvas, draw the text on it with 500px font-size. But it wasn't perfect when getting bigger at the start, so I made several canvases and put them on visible canvas by steps.

    It is just for some idea how it can be solved, not elegant method. Better solution for this example will be SVG!

    let canvas = document.getElementById('canvas');
    let context = this.canvas.getContext('2d');
    let tick = 0;
    
    
    let scale_steps = [2,4,7,10];
    let canvases = [];
    
    for(let i=0; i < scale_steps.length; i++){
        canvases[i] = document.createElement('canvas');
        canvases[i].width = canvases[i].height = 500 * scale_steps[i];
        let ctx = canvases[i].getContext('2d');
        ctx.font = `bold ${scale_steps[i] * 50}px Sans-serif`;
        ctx.textBaseline = 'top';
        ctx.fillStyle = 'black';
        ctx.fillText('Hello, world!', 0, 0);
    }
    
    let loop = function() {
    context.clearRect(0, 0, canvas.width, canvas.height);
        
    let scale = 1 + tick * 0.005;
    let index = scale < 4 ? Math.floor(scale-1) : 3;
    context.save();
    context.scale(scale,scale);
    context.drawImage(canvases[index],0,0,scale_steps[index]
                       *500,scale_steps[index]*500,0,0,500,500);   
    context.restore();
    tick++;  
    window.requestAnimationFrame(loop);
    }
    
    window.requestAnimationFrame(loop);
    <canvas id="canvas" width="500" height="500"></canvas>

    Also you can add the text as a SVG-path:

    let canvas = document.getElementById('canvas');
    let ctx = canvas.getContext('2d');
    let w,h;
    w = h = canvas.width = canvas.height = 500;
    
    draw.t = 0;
    
    function draw(){
        ctx.clearRect(0, 0, w, h);
    
        let scale = 1 + draw.t * 0.005;
        ctx.save();
        ctx.scale(scale, scale);
    
        ctx.fill(new Path2D("M0 0.72l10.42 0 0 15.41 15.36 0 0 -15.41 10.42 0 0 40.41 -10.42 0 0 -17.13 -15.36 0 0 17.13 -10.42 0 0 -40.41zm76.21 25.17l0 2.77 -22.67 0c0.23,2.27 1.05,3.97 2.46,5.11 1.41,1.13 3.38,1.7 5.91,1.7 2.04,0 4.13,-0.3 6.26,-0.9 2.14,-0.61 4.34,-1.53 6.6,-2.75l0 7.48c-2.29,0.86 -4.58,1.5 -6.88,1.95 -2.29,0.43 -4.58,0.65 -6.87,0.65 -5.49,0 -9.75,-1.39 -12.78,-4.17 -3.04,-2.79 -4.57,-6.7 -4.57,-11.73 0,-4.95 1.5,-8.84 4.48,-11.66 2.99,-2.84 7.1,-4.25 12.34,-4.25 4.75,0 8.57,1.43 11.43,4.3 2.86,2.87 4.29,6.7 4.29,11.5zm-9.98 -3.22c0,-1.85 -0.53,-3.33 -1.61,-4.46 -1.08,-1.13 -2.47,-1.69 -4.21,-1.69 -1.87,0 -3.4,0.52 -4.57,1.59 -1.17,1.05 -1.9,2.57 -2.19,4.56l12.58 0zm17.31 -23.67l9.7 0 0 42.13 -9.7 0 0 -42.13zm19 0l9.7 0 0 42.13 -9.7 0 0 -42.13zm33.43 18.02c-2.15,0 -3.79,0.76 -4.92,2.31 -1.13,1.54 -1.69,3.76 -1.69,6.67 0,2.9 0.56,5.12 1.69,6.67 1.13,1.54 2.77,2.31 4.92,2.31 2.11,0 3.73,-0.77 4.85,-2.31 1.11,-1.55 1.68,-3.77 1.68,-6.67 0,-2.91 -0.57,-5.13 -1.68,-6.67 -1.12,-1.55 -2.74,-2.31 -4.85,-2.31zm0 -6.93c5.22,0 9.29,1.4 12.22,4.22 2.93,2.81 4.4,6.71 4.4,11.69 0,4.98 -1.47,8.87 -4.4,11.68 -2.93,2.82 -7,4.22 -12.22,4.22 -5.23,0 -9.33,-1.4 -12.27,-4.22 -2.95,-2.81 -4.43,-6.7 -4.43,-11.68 0,-4.98 1.48,-8.88 4.43,-11.69 2.94,-2.82 7.04,-4.22 12.27,-4.22zm24.65 20.56l9.76 0 0 8.25 -6.71 10.1 -5.76 0 2.71 -10.1 0 -8.25zm36.64 -19.84l9.43 0 5.08 20.9 5.12 -20.9 8.09 0 5.09 20.68 5.11 -20.68 9.42 0 -7.98 30.32 -10.59 0 -5.11 -20.84 -5.09 20.84 -10.58 0 -7.99 -30.32zm68.35 6.21c-2.14,0 -3.79,0.76 -4.91,2.31 -1.13,1.54 -1.69,3.76 -1.69,6.67 0,2.9 0.56,5.12 1.69,6.67 1.12,1.54 2.77,2.31 4.91,2.31 2.12,0 3.73,-0.77 4.85,-2.31 1.12,-1.55 1.68,-3.77 1.68,-6.67 0,-2.91 -0.56,-5.13 -1.68,-6.67 -1.12,-1.55 -2.73,-2.31 -4.85,-2.31zm0 -6.93c5.22,0 9.3,1.4 12.23,4.22 2.92,2.81 4.39,6.71 4.39,11.69 0,4.98 -1.47,8.87 -4.39,11.68 -2.93,2.82 -7.01,4.22 -12.23,4.22 -5.23,0 -9.32,-1.4 -12.27,-4.22 -2.94,-2.81 -4.42,-6.7 -4.42,-11.68 0,-4.98 1.48,-8.88 4.42,-11.69 2.95,-2.82 7.04,-4.22 12.27,-4.22zm46.17 8.98c-0.85,-0.4 -1.69,-0.7 -2.53,-0.88 -0.83,-0.19 -1.68,-0.29 -2.53,-0.29 -2.48,0 -4.39,0.8 -5.73,2.39 -1.34,1.6 -2.02,3.89 -2.02,6.87l0 13.97 -9.7 0 0 -30.32 9.7 0 0 4.99c1.25,-1.99 2.67,-3.44 4.28,-4.35 1.62,-0.91 3.55,-1.36 5.8,-1.36 0.33,0 0.68,0.01 1.06,0.03 0.37,0.03 0.92,0.1 1.64,0.18l0.03 8.77zm4.83 -20.07l9.7 0 0 42.13 -9.7 0 0 -42.13zm39.62 16.24l0 -16.24 9.76 0 0 42.13 -9.76 0 0 -4.38c-1.33,1.78 -2.8,3.09 -4.4,3.92 -1.61,0.82 -3.47,1.23 -5.58,1.23 -3.74,0 -6.8,-1.48 -9.2,-4.45 -2.4,-2.96 -3.6,-6.78 -3.6,-11.45 0,-4.68 1.2,-8.49 3.6,-11.46 2.4,-2.97 5.46,-4.45 9.2,-4.45 2.09,0 3.94,0.41 5.56,1.25 1.62,0.83 3.09,2.14 4.42,3.9zm-6.38 19.63c2.07,0 3.66,-0.76 4.75,-2.28 1.09,-1.51 1.63,-3.71 1.63,-6.59 0,-2.89 -0.54,-5.08 -1.63,-6.6 -1.09,-1.52 -2.68,-2.27 -4.75,-2.27 -2.06,0 -3.64,0.75 -4.73,2.27 -1.09,1.52 -1.63,3.71 -1.63,6.6 0,2.88 0.54,5.08 1.63,6.59 1.09,1.52 2.67,2.28 4.73,2.28zm28.55 -34.15l9.76 0 0 15.51 -1.39 11.32 -6.99 0 -1.38 -11.32 0 -15.51zm0 30.76l9.76 0 0 9.65 -9.76 0 0 -9.65z"));
    
        ctx.restore();
        draw.t++;  
        window.requestAnimationFrame(draw);
    }
    
    window.requestAnimationFrame(draw);
    <canvas id="canvas"></canvas>