Canvas on resize - keep a maximal width or height

2019-07-04 01:09发布

I have a question for a html5 game using canvas in javascript.

I would like that the canvas showed to the player contains either 1920 of canvas.width, either 1080 of canvas.height in order to not see the padding. That means that I don't want to keep the (optimal) game ratio 16/9 if the player resize his screen with an other ratio, but I want to keep the viewport ratio 16/9 to avoid stretching (using canvas.style.***)

Let me take an example of a player with a resizable screen (window.innerWidth = 500 and window.innerHeight = 1600), as 1600/500 > 1920 / 1080, I would like that the player can see 1920 of the game width and "less than 1080" of the game height, preventing viewport stretching.

Agar.io is an other good example.

Thank you very much !

1条回答
叛逆
2楼-- · 2019-07-04 01:40

To fit but will leave empty areas on the sides or top bottom if aspects do not match.

var scale = Math.min(innerWidth / canvas.width, innerHeight / canvas.height);

or to fill but will clip canvas if aspects do not match

var scale = Math.max(innerWidth / canvas.width, innerHeight / canvas.height);

for display 1600 by 500 and canvas 1920 by 1080

1600 / 1920 = 0.8333;
500 / 1080 = 0.463;

thus to fit canvas will be 0.463 * (1920,1080) = (889,500) empty on sides

and to fill canvas will be 0.8333 * (1920,1080) = (1600,900) clipped top and bottom.

More info on scale and fit can be found below.

If you are scaling to fill the canvas you will need to account for the clipped area and find the offset to the top left corner of the canvas (this will be off the page).

var leftOffset = 0;
var topOffset = 0;
var canW = scale * canvas.width;
var canH = scale * canvas.height;
if(canW > innerWidth ){
    leftOffset = ((canW - innerWidth) / canW) * 1920 / 2;
}else
if(canH > innerHeight ){
    topOffset = ((canH - innerHeight) / canH) * 1080 / 2;
}

Your canvas will fill the page innerWidth and innerHeight but you will need to offset all rendering. This can be done by setting the transform to the correct offsets

ctx.setTransform(1,0,0,1,-leftOffset, -topOffset);

The canvas display size will be

canvas.style.width = innerWidth + "px"; 
canvas.style.height = innerHeight + "px";

and the canvas resolution will be

canvas.width = 1920 - (leftOffset * 2);
canvas.height = 1080 - (topOffset * 2);

Below is an example of the code in action. Very lazy coding, just to show it working should really not create and destroy canvas on resizes. Also as fiddle because it has resizable panels to test with.

 var canvas;
var createCanvas = function(){
    if(canvas !== undefined){
         document.body.removeChild(canvas);
    
    }
    var canWidth = 1920;
    var canHeight = 1080;

    var scale = Math.max(innerWidth /canWidth, innerHeight / canHeight);
    var leftOffset = 0;
    var topOffset = 0;
    var canW = scale * canWidth;
    var canH = scale * canHeight;
    if(canW > innerWidth ){
        leftOffset = ((canW - innerWidth) / canW) * canWidth / 2;
    }else
    if(canH > innerHeight ){
        topOffset = ((canH - innerHeight) / canH) * canHeight / 2;
    }

    canvas = document.createElement("canvas");
    canvas.style.position = "absolute";
    canvas.style.top = "0px";
    canvas.style.left = "0px";
    canvas.style.width = innerWidth + "px"; 
    canvas.style.height = innerHeight + "px";
    canvas.width = canWidth - (leftOffset * 2);
    canvas.height = canHeight - (topOffset * 2);
    document.body.appendChild(canvas);
    var ctx = canvas.getContext("2d");


    ctx.setTransform(1,0,0,1,-leftOffset, -topOffset);
    ctx.beginPath();
    ctx.arc(canWidth / 2, canHeight/2, 400,0,Math.PI * 2);
    ctx.stroke();
}

window.addEventListener('resize',createCanvas);
createCanvas();


Scaling to fit

Means that the whole image will be visible but there may be some empty space on the sides or top and bottom if the image is not the same aspect as the canvas. The example shows the image scaled to fit. The blue on the sides is due to the fact that the image is not the same aspect as the canvas.

enter image description here

Scaling to fill

Means that the image is scaled so that all the canvas pixels will be covered by the image. If the image aspect is not the same as the canvas then some parts of the image will be clipped. The example shows the image scaled to fill. Note how the top and bottom of the image are no longer visible.

enter image description here

Example Scale to fit

var image = new Image();
image.src = "imgURL";
image.onload = function(){
    scaleToFit(this);
}

function scaleToFit(img){
    // get the scale
    var scale = Math.min(canvas.width / img.width, canvas.height / img.height);
    // get the top left position of the image
    var x = (canvas.width / 2) - (img.width / 2) * scale;
    var y = (canvas.height / 2) - (img.height / 2) * scale;
    ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
}

Example Scale to fill

var image = new Image();
image.src = "imgURL";
image.onload = function(){
    scaleToFill(this);
}

function scaleToFill(img){
    // get the scale
    var scale = Math.max(canvas.width / img.width, canvas.height / img.height);
    // get the top left position of the image
    var x = (canvas.width / 2) - (img.width / 2) * scale;
    var y = (canvas.height / 2) - (img.height / 2) * scale;
    ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
}

The only differance between the two functions is getting the scale. The fit uses the min fitting scale will the fill uses the max fitting scale.

查看更多
登录 后发表回答