Масштабирование в соответствии с фиксированным соотношением сторон.
Вы можете использовать CanvasRenderingContext2D.setTransform , чтобы масштабировать и позиционировать преобразование, чтобы соответствовать и центрировать содержимое.
Вам потребуется эталонное разрешение, которое определяет исходную шкалу координат 1
и аспект. Например ...
const refRes = {width: 1000, height: 1000};
Масштабирование по размеру
Затем вы можете рассчитать масштаб и начало координат, чтобы подогнать и отцентрировать содержимое на холсте заданного размера. Это делается с использованием минимального масштабируемого размера для масштабирования содержимого. Например ...
// 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
При сохранении аспекта могут быть неиспользованные пиксели слева и справа или выше и ниже в зависимости от аспекта холста.
Масштаб для заполнения
Вы можете масштабировать, чтобы заполнить, что обрезает содержимое, но гарантирует, что все пиксели используются. Просто используйте максимальный масштабированный размер. Например ...
// Use max res to scale to fill
const scale = Math.max(canvas.width / refRes.width, canvas.height / refRes.height);
Демонстрация
Демонстрация показывает содержание, масштабированное по размеру холста, размер которого изменяется случайным образом. Содержимое отображается в исходных системах координат, а двумерное преобразование используется для масштабирования по размеру и центру.
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>