如何调整 canvas 中包含我的元素的大小?

How do I resize my canvas with my elements in it?

背景信息

我目前正在做一个项目,我希望背景是空白的 canvas,球从墙上弹跳。

到目前为止我已经成功了,但是我遇到了一个问题。 每当我调整浏览器 window 大小时,我的 canvas 或其中的球都不会跟进。我不知道我做错了什么。

codepen

代码

const canvas = document.querySelector('#responsive-canvas')

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

let c = canvas.getContext('2d');

function Circle(x, y, dx, dy, radius, colour) {
  this.x = x;
  this.y = y;
  this.dx = dx;
  this.dy = dy;
  this.radius = radius;
  this.colour = colour;


  this.draw = function() {

    this.getNewColour = function() {
      let symbols, colour;

      symbols = "0123456789ABCDEF";

      colour = "#";

      for (let i = 0; i < 6; i++) {
        colour += symbols[Math.floor(Math.random() * 16)];
      }
      c.strokeStyle = colour;
      c.fillStyle = colour;
    }
    this.getNewColour();


    this.x = x;
    this.y = y;
    this.radius = radius;
    //this.getNewColour().colour = colour;

    c.beginPath();
    c.arc(x, y, radius, 0, Math.PI * 2, false);
    //  c.strokeStyle = 'blue';
    c.stroke();
    //c.fill();
  }

  this.update = function() {
    this.x = x;
    this.y = y;
    this.dx = dx;
    this.dy = dy;
    this.radius = radius;

    if (x + radius > innerWidth || x - radius < 0) {
      dx = -dx;
    }

    if (y + radius > innerHeight || y - radius < 0) {
      dy = -dy;
    }
    x += dx;
    y += dy;

    this.draw();
  }
}

let circleArr = [];

for (let i = 0; i < 23; i++) {

  let radius = 50;
  let x = Math.random() * (innerWidth - radius * 2) + radius;
  let y = Math.random() * (innerHeight - radius * 2) + radius;
  let dx = (Math.random() - 0.5);
  let dy = (Math.random() - 0.5);

  circleArr.push(new Circle(x, y, dx, dy, radius));
};




function animate() {
  requestAnimationFrame(animate);
  c.clearRect(0, 0, innerWidth, innerHeight)

  for (var i = 0; i < circleArr.length; i++) {
    circleArr[i].update();
  }
};



animate();
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}


/* BODY
*/

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
}
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title></title>
  <meta name="description" content="">
  <link rel="stylesheet" href="../dist/css/style.css">

  <body>
    <canvas id="responsive-canvas"></canvas>
  </body>
  <script src="js/canvas.js"></script>

</html>

你可以使用 ctx.scale(x,y); 要按给定因子缩放 canvas 上的所有内容,X 和 Y 分别在 X 轴和 Y 轴上缩放。

您可能希望使用 canvas 重置 ctx.resetTransform()ctx.setTransform(1,0,0,1,0,0); 然后将背景从 0,0 绘制到 canvas.width 和 canvas.height 然后绘制所有内容(所有圆圈),最后将缩放设置为您想要的值。

如果你想改变宽度和高度,你只需要在与`{border-box}相同的CSS区域添加宽度和高度。

但是如果你想让里面的图片不被拉伸,你需要使用上面的注释方法在canvas中专门访问heigh。

最好使用:

width:() => document.documentElement.clientWidth

首先你必须修复你的碰撞逻辑,然后你可以根据需要安全地更改 canvas 大小。

始终完全解决冲突。

未解决的碰撞意味着您的 sim 处于不可能的状态(例如墙内的球)。从那时起,以下状态的意义将减少。

修复

球移出 canvas 的原因是,如果它们在外面,您不会将它们移回 canvas。

此外,当您在球击中侧面时改变球的方向时,您无法确保方向是正确的符号。

    // if the dx = 1 and x > innerWidth + 1000 then dx becomes -1
    // Next tine x is still > innerWidth + 1000 and you flip the sign of dx again to 1,
    // Then dx to -1 and so on. You never move the ball 
    if(x + radius > innerWidth || x - radius < 0) {
        dx = -dx;  
    } 

系统地进行碰撞测试,这样当运动场 (canvas) 改变大小时,您就不会得到意想不到的结果

更新时,先移动再检查碰撞。如果发生碰撞,请再次移动球以确保不会发生魔法球在墙上的事情。

update() {
    this.x += this.dx;
    this.y += this.dy;
    if (this.x < this.radius) {
        this.x = this.radius;
        this.dx = Math.abs(this.dx);
    }
    if (this.y < this.radius) {
        this.y = this.radius;
        this.dy = Math.abs(this.dy);
    }
    if (this.x > ctx.canvas.width - this.radius) {
        this.x = ctx.canvas.width - this.radius;
        this.dx = -Math.abs(this.dx);
    }
    if (this.y > ctx.canvas.height - this.radius) {
        this.y = ctx.canvas.height - this.radius;
        this.dy = -Math.abs(this.dy);
    }
}

调整大小 canvas

随着碰撞问题的解决,您现在可以随时更改 canvas 大小。一种方法是在 animate 函数中进行。

如果 canvas 大小与页面大小不匹配,请更改 canvas 大小以匹配。

function animate () {
    if (ctx.canvas.width !== innerWidth || ctx.canvas.height !== innerHeight) {
        ctx.canvas.width = innerWidth;
        ctx.canvas.height = innerHeight;
    } else {
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
    }

    // ... do stuff

    requestAnimationFrame(animate);
} 

注意 更改 canvas 大小也会清除 canvas,这就是清除 else 子句的原因。

复制粘贴演示

代码中有很多不好的地方。您可以将下面的代码片段复制粘贴到代码笔中,或者使用上面的信息按照您的风格修改代码。

const canvas = document.querySelector('#responsive-canvas')

canvas.width = innerWidth;
canvas.height = innerHeight;

// Set constants in one place so you can make changes quickly and easily
const DEFAULT_RADIUS = 50;
const MAX_SPEED = 5;
const CIRCLE_COUNT = 100;
Math.TAU = Math.PI * 2

// Use function to do repetitive code
Math.rand = (min, max) => Math.random() * (max - min) + min;  // 
function randomHexColor() { 
    return "#" + ((Math.random() * 0xFFFFFF | 0).toString(16).padStart(6,"0"));
}

// pulral names for arrays and variables that do not change should be constants
const circles = [];
const ctx = canvas.getContext('2d');

requestAnimationFrame(animate); // start frame renderer with a request, dont call it directly



function Circle( // using default params to set random values
        radius = Math.rand(DEFAULT_RADIUS/4, DEFAULT_RADIUS), // radius must be first argument as its used to set random x, and y
        x = Math.rand(radius, ctx.canvas.width - radius), 
        y = Math.rand(radius, ctx.canvas.height - radius),
        dx = Math.rand(-MAX_SPEED, MAX_SPEED),
        dy = Math.rand(-MAX_SPEED, MAX_SPEED),
        colour = randomHexColor()
    ) {

    this.x = x;
    this.y = y;
    this.dx = dx;
    this.dy = dy;
    this.radius = radius;
    this.colour = colour;
}
// Define Circle functions as prototype outside the function Circle (runs faster)
Circle.prototype = {
    draw() {
        ctx.strokeStyle = ctx.fillStyle = this.colour; 
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.TAU);
        ctx.stroke();
    },
    update() {
        this.x += this.dx;
        this.y += this.dy;
        if (this.x < this.radius) {
            this.x = this.radius;
            this.dx = Math.abs(this.dx);
        }
        if (this.y < this.radius) {
            this.y = this.radius;
            this.dy = Math.abs(this.dy);
        }
        if (this.x > ctx.canvas.width - this.radius) {
            this.x = ctx.canvas.width - this.radius;
            this.dx = -Math.abs(this.dx);
        }
        if (this.y > ctx.canvas.height - this.radius) {
            this.y = ctx.canvas.height - this.radius;
            this.dy = -Math.abs(this.dy);
        }
    }
};
for (let i = 0; i < CIRCLE_COUNT; i++) { circles.push(new Circle()) } 
function animate () {
    if (ctx.canvas.width !== innerWidth || ctx.canvas.height !== innerHeight) {
        ctx.canvas.width = innerWidth;
        ctx.canvas.height = innerHeight;
    } else {
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
    }

    // use for of loop (saves having to mess with index)
    for (const circle of circles) {
            circle.update(); // seperate update and draw
            circle.draw()
    }
    requestAnimationFrame(animate);
}