在不损失质量的情况下调整 canvas 到视口

Resize canvas to viewport without loosing quality

我正在尝试让 canvas 填充整个视口并在用户调整浏览器大小时调整大小 window。 但是,当我尝试使用 100% 的尺寸时,它会降低质量。

我是 JavaScript 编程的新手。

有人可以帮我吗?

我想出了一个办法:

我计算了一个比例因子,通过它我缩放了大小,而不是绘制的每个元素的位置。 我不想缩放位置,因为那样一些元素可能会在屏幕之外。 因此我不能按照评论中的建议使用 setTransform。 这是我用来计算比例因子的代码。 如果有人有改进,请在下面的评论中分享!

if (window.screen.width > window.screen.height)
    scalingFactor = window.screen.width * window.devicePixelRatio / 1280;
else
    scalingFactor = window.screen.height * window.devicePixelRatio / 720;

在丢失当前数据的情况下调整 canvas 大小的最简单方法是存储数据,然后调整 canvas 大小(清除它),然后将数据重新绘制到 canvas。那看起来像这样:

const data = ctx.getImageData( 0, 0, canvas.width, canvas.height );
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx.putImageData( data, 0, 0 );

在最纯粹、最简单的示例中,它看起来像这样:

/* This method does the resize by storing the data and redrawing it. It loses any data outside of the screen, though. */
function onResize( event ){

  const data = ctx.getImageData( 0, 0, canvas.width, canvas.height );
  
  canvas.width = innerWidth;
  canvas.height = innerHeight;
  
  ctx.putImageData( data, 0, 0 );

}

/* This method is for drawing some content to help visualisation. */
function onRedraw(){
    
  const r = v => Math.floor(Math.random() * v);
  
  canvas.width = innerWidth;
  canvas.height = innerHeight;

  ctx.fillStyle = `rgb(${r(255)},${r(255)},${r(255)})`;
  ctx.fillRect( 0, 0, innerWidth, innerHeight );
  ctx.fillStyle = `rgb(${r(255)},${r(255)},${r(255)})`;
  ctx.fillRect( 0, 0, innerWidth / 2, innerHeight / 2 );
  ctx.fillRect( innerWidth / 2, innerHeight / 2, innerWidth / 2, innerHeight / 2 );

}

window.addEventListener( 'resize', onResize );

const canvas = document.createElement( 'canvas' );
const ctx = canvas.getContext( '2d' );
const reset = document.getElementById( 'reset' );

document.body.appendChild( canvas );

onRedraw();

redraw.addEventListener( 'click', onRedraw );
body { padding: 0; margin: 0; overflow: hidden; }
button { position: fixed; top: 20px; left: 20px; z-index: 1; }
<button id="redraw">Redraw</button>

但是,当您的 window 增长和收缩时,您会丢失在 canvas 之外绘制的数据,因为数据永远不会保存。我们可以为我们的图形保留一个缓冲区,这可以简化一些事情,但会使代码稍微复杂一些。在这种情况下,我们可以使用第二个 canvas 来保存我们之前绘制的所有最大尺寸屏幕数据,仅供参考:

/* This method does the resize by storing the data and redrawing it. It retains data outside the screen in a buffer that holds the maximum size ever drawn. */
function onResize( event ){

  const data = bufferCtx.getImageData( 0, 0, bufferCanvas.width, bufferCanvas.height );
  
  bufferCanvas.width = Math.max( bufferCanvas.width, innerWidth );
  bufferCanvas.height = Math.max( bufferCanvas.height, innerHeight );
  
  bufferCtx.putImageData( data, 0, 0 );
  bufferCtx.drawImage( canvas, 0, 0 );
  
  canvas.width = innerWidth;
  canvas.height = innerHeight;
  
  ctx.drawImage( bufferCanvas, 0, 0 );

}

/* This method is for drawing some content to help visualisation. */
function onRedraw(){
    
  const r = v => Math.floor(Math.random() * v);
  
  canvas.width = innerWidth;
  canvas.height = innerHeight;

  ctx.fillStyle = `rgb(${r(255)},${r(255)},${r(255)})`;
  ctx.fillRect( 0, 0, innerWidth, innerHeight );
  ctx.fillStyle = `rgb(${r(255)},${r(255)},${r(255)})`;
  ctx.fillRect( 0, 0, innerWidth / 2, innerHeight / 2 );
  ctx.fillRect( innerWidth / 2, innerHeight / 2, innerWidth / 2, innerHeight / 2 );

}

window.addEventListener( 'resize', onResize );

const canvas = document.createElement( 'canvas' );
const ctx = canvas.getContext( '2d' );
const bufferCanvas = document.createElement( 'canvas' );
const bufferCtx = bufferCanvas.getContext( '2d' );
const reset = document.getElementById( 'reset' );

document.body.appendChild( canvas );

onRedraw();

redraw.addEventListener( 'click', onRedraw );
body { padding: 0; margin: 0; overflow: hidden; }
button { position: fixed; top: 20px; left: 20px; z-index: 1; }
<button id="redraw">Redraw</button>

如果您宁愿使用完全不丢失数据和 'rescale' 最好的 canvas,也许最好使用这样的东西:

/* This method does the resize by just redrawing the canvas from a buffer: */
function onResize( event ){
  
  bufferCanvas.width = canvas.width;
  bufferCanvas.height = canvas.height;
  bufferCtx.drawImage( canvas, 0, 0 );
  
  canvas.width = innerWidth;
  canvas.height = innerHeight;
  
  ctx.drawImage( bufferCanvas, 0, 0, canvas.width, canvas.height );

}

/* This method is for drawing some content to help visualisation. */
function onRedraw(){
    
  const r = v => Math.floor(Math.random() * v);
  
  canvas.width = innerWidth;
  canvas.height = innerHeight;

  ctx.fillStyle = `rgb(${r(255)},${r(255)},${r(255)})`;
  ctx.fillRect( 0, 0, innerWidth, innerHeight );
  ctx.fillStyle = `rgb(${r(255)},${r(255)},${r(255)})`;
  ctx.fillRect( 0, 0, innerWidth / 2, innerHeight / 2 );
  ctx.fillRect( innerWidth / 2, innerHeight / 2, innerWidth / 2, innerHeight / 2 );

}

window.addEventListener( 'resize', onResize );

const canvas = document.createElement( 'canvas' );
const ctx = canvas.getContext( '2d' );
const bufferCanvas = document.createElement( 'canvas' );
const bufferCtx = bufferCanvas.getContext( '2d' );
const reset = document.getElementById( 'reset' );

document.body.appendChild( canvas );

onRedraw();

redraw.addEventListener( 'click', onRedraw );
body { padding: 0; margin: 0; overflow: hidden; }
button { position: fixed; top: 20px; left: 20px; z-index: 1; }
<button id="redraw">Redraw</button>