Reputation:
I want to use a canvas thats max 1000px x 1000px. But if the screen is smaller, or the window becomes smaller to automatically adjust the width/height based on that while maintaining original aspect ratio. example: If width=500px then canvas = 500x500, not 500x1000. if height = 100, then canvas = 100x100. I dont mind using javascript, css, html to do this. thank you.
Upvotes: 0
Views: 4868
Reputation: 54069
You can use CanvasRenderingContext2D.setTransform to scale and position the transform to fit and center content.
You will need a reference resolution which defines the original coordinate scale 1
and the aspect. Eg...
const refRes = {width: 1000, height: 1000};
Then you can calculate the scale and origin to fit and center content to a given sized canvas. This is done by using the minimum scaled dimension to scale the content. Eg...
// Get the scale to fit content to the canvas
const scale = Math.min(canvas.width / refRes.width, canvas.height / refRes.height);
// set the origin so that the scaled content is centered on the canvas
const origin = {
x: (canvas.width - refRes.width * scale) / 2,
y: (canvas.height - refRes.height * scale) / 2
};
// Set the transform to scale and center on canvas
ctx.setTransform(scale, 0, 0, scale, origin.x, origin.y);
// Then render your content using the original coordinates.
ctx.fillRect(0, 0, 1000, 1000); // will fit any sized canvas
As this maintains the aspect there may be unused pixels left and right or above and below depending on the canvas aspect.
You can scale to fill, which will crop the content but ensures that all pixels are use. Just use the maximum scaled dimension. Eg...
// Use max res to scale to fill
const scale = Math.max(canvas.width / refRes.width, canvas.height / refRes.height);
Demo shows content scaled to fit a canvas that has its size changed randomly. The content is rendered in the original coordinate systems and the 2D transform is used to scale to fit and center.
requestAnimationFrame(mainLoop);
const ctx = canvas.getContext("2d");
const size = 1000;
Math.TAU = Math.PI * 2;
Math.randI = (m, M) => Math.random() * (M - m) + m | 0; // for unsigned int32
Math.nearZero = val => Math.abs(val) < 1e-3;
const refRes = {width: size, height: size};
renderContent();
// State for canvas size changes
var xRes = canvas.width, yRes = canvas.height;
var xResC = xRes, yResC = yRes; // current resolution
var xResD = 0, yResD = 0; // resolution delta change
const rate = 0.2; // rate of canvas size change
// WARNING there is no bounds checking for canvas size.
// If rate < 0 || rate > 0.5 you MUST check that canvas size
// is safe before setting its width and height
function scaleToFit() {
const scale = Math.min(canvas.width / refRes.width, canvas.height / refRes.height);
ctx.setTransform(
scale, 0, 0, scale,
(canvas.width - refRes.width * scale) / 2,
(canvas.height - refRes.height * scale) / 2
);
}
function mainLoop() {
xResC += (xResD = (xResD += (xRes - xResC) * rate) * rate);
yResC += (yResD = (yResD += (yRes - yResC) * rate) * rate);
const w = xResC | 0;
const h = yResC | 0;
if (w !== canvas.width || h !== canvas.height) {
canvas.width = w;
canvas.height = h;
renderContent();
}
if(Math.nearZero(xResD) && Math.nearZero(yResD)) {
xRes = Math.randI(30, 300);
yRes = Math.randI(30, 200);
}
requestAnimationFrame(mainLoop);
}
function renderContent() {
scaleToFit();
ctx.fillStyle = "#Faa";
ctx.fillRect(0,0,size,size);
ctx.fillStyle = "#8aF";
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2 - 4, 0, Math.TAU);
ctx.fill();
ctx.fillStyle = "#FF8";
ctx.fillRect(
(size - size * Math.SQRT1_2) / 2, (size - size * Math.SQRT1_2) / 2,
size * Math.SQRT1_2, size * Math.SQRT1_2
);
ctx.lineWidth = 10;
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2 - 4, 0, Math.TAU);
ctx.rect(
(size - size * Math.SQRT1_2) / 2, (size - size * Math.SQRT1_2) / 2,
size * Math.SQRT1_2, size * Math.SQRT1_2
);
ctx.stroke();
}
canvas {
border: 1px solid black;
}
<canvas id="canvas"></canvas>
Upvotes: 4