Search code examples
javascriptcanvasrotatetransformscaletransform

Scaling of JavaScript canvas looks strange after rotation


I have an html canvas that is stretched to the browser window dimensions in an JS script. I want to keep the fit of the canvas to the window dimensions (via ctx.scale or ctx.setTransform), but rotate it by 90° when the window is vertical, i.e. window.innerHeight > window.innerWidth. However, I see some strange behavior in the vertical case and don't know what I'm missing.

My JavaScript code is below:

var canvas = document.getElementById("GameCanvas");
var ctx = canvas.getContext("2d");
canv_start_w = canvas.width;
canv_start_h = canvas.height;

// drawing functions
function set_canvas_bg() {  
    ctx.fillStyle = "lightblue";
    ctx.fillRect(0,0,canvas.width, canvas.height);
}
function draw_line() {
    ctx.beginPath();
    ctx.lineWidth="3";
    ctx.strokeStyle="black";
    ctx.moveTo(canv_start_w/16, canv_start_h/2);
    ctx.lineTo(15*canv_start_w/16, canv_start_h/2);
    ctx.stroke();
    ctx.closePath();
}
function draw_circ() {
    ctx.beginPath();
    ctx.fillStyle="black";
    ctx.arc(canv_start_w/2, canv_start_h/2, canv_start_w/10, 0, 2*Math.PI);
    ctx.fill();
    ctx.closePath();
}

// canvas transformation functions
function resize_canvas() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
}
function scale_ctx() {
    var smaller_dim = Math.min(canvas.width, canvas.height);
    var larger_dim = Math.max(canvas.width, canvas.height)
    var xscale = larger_dim/canv_start_w;
    var yscale = smaller_dim/canv_start_h;
    ctx.setTransform(xscale, 0, 0, yscale, 0, 0);
}
function rotate_ctx(deg) {
    ctx.rotate((deg/360)*(2*Math.PI));
}

function update() {

    resize_canvas();
    scale_ctx();

    // distinction: vertical or horizontal window?
    if (window.innerWidth < window.innerHeight) {
        // CASE 1: vertical
        rotate_ctx(90);
        ctx.translate(0, -canvas.width); // move back into view
    } else {
        // CASE 2: horizontal
        rotate_ctx(0);
        ctx.translate(0, 0); // move back into view
    }

    // drawing elements to canvas
    set_canvas_bg();
    draw_circ();
    draw_line();
    requestAnimationFrame(update);
}

update();

Solution

  • Everytime the canvas dimensions are changed, the context transformations (i.e. rotate, scale) are deleted. The working script can be found and experimented with here: https://jsfiddle.net/nicoa47/3fsan6c0/

    The three-part solution is

    (1) to reset the transformations at the beginning of each update loop via

    ctx.setTransform(1, 0, 0, 1, 0, 0);
    

    (2) to translate the context to window center and translate back after rotating/scaling (which ensures rotation around window's center) via

    ctx.translate(window.innerWidth/2, window.innerHeight/2);
    // rotate...
    // scale...
    ctx.translate(-window.innerWidth/2, -window.innerHeight/2);
    

    In the vertical case, the arguments of the second translate need to be swapped.

    (3) to change the scaling function based on the current scale. That means, create new variables

    var canv_current_w = window.innerWidth;
    var canv_current_h = window.innerHeight;
    

    Which keep track of the last frame's dimensions, i.e. at the end of the update loop set:

    canv_current_w = window.innerWidth;
    canv_current_h = window.innerHeight;
    

    Again, swap values in the vertical case. Replace canv_start_w, canv_start_h with these new variables in the scale function.