Canvas实战:绘制一个动态小球

3,313 阅读3分钟

1、新建画布

canvas首先是空白的,我们首先设置canvas的宽高,并找到渲染上下文

<body>
    <!-- 新建canvas元素 -->
    <canvas id="canvas"></canvas>
  </body>
window.onload = () => {
   // 获取canvasDom
  const canvas = document.getElementById("canvas");
  // 手动设置宽高
  canvas.width = 800;
  canvas.height = 800;
  // 获取上下文
  const ctx = canvas.getContext("2d");
};

2、绘制边框

此时页面已经可以看到画布了,我们可以更明显的一些绘制一个边框

window.onload = () => {
   .....
  // 获取canvas上下文
  const ctx = canvas.getContext("2d");
  // 绘制边框
  renderBorder(ctx);
};
function renderBorder(ctx) {
  // 首先获取画布的宽高
  const width = ctx.canvas.width;
  const height = ctx.canvas.height;
  ctx.beginPath(); // 开始一个新的路径
  ctx.moveTo(00); // 将路径的起始点移动到左上角
  ctx.lineTo(width, 0); // 使用直线连接到右上角(并不绘制)
  ctx.lineTo(width, height); // ...右下角
  ctx.lineTo(0, height); // ...左下角
  ctx.closePath(); // 结束一个新的路径
  ctx.stroke(); // 绘制当前已知路径
}

3、绘制球

此时我们已经可以看到边框了,首先需要新建一个“球”对象,球对象有如下一些属性,再实现如何绘制这个“球”对象,这里涉及到canvas绘制圆弧路径的方法,比较特殊的这里是弧度

void ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

  • x:圆弧中心点x轴坐标
  • y: 圆弧中心点y轴坐标
  • radius: 半径
  • startAngle:圆弧起始点
  • endAngle: 圆弧结束点
  • anticlockwis(可选):Boolean类型,如果为true,则逆时针方向绘制,反之,顺时针方向绘制
window.onload = () => {
    .....
  // 绘制球
  let ball = {
    x0// 当前x轴坐标
    y0// 当前y轴坐标
    radius10// 半径
    g0.1// 重力加速度
    vx8// x轴移动速度
    vy4// y轴移动速度
    color"blue"// 颜色
  };
  renderBall(ctx, ball);
};
// 绘制一个球
function renderBall(ctx, ball) {
  const x = ball.x + ball.radius// 圆弧中心(圆心)的 x 轴坐标。
  const y = ball.y + ball.radius// 圆弧中心(圆心)的 y 轴坐标。
  const radius = ball.radius// 半径
  const startAngle = 0// 圆弧的起始点
  const endAngle = 2 * Math.PI// 圆弧的结束点
  ctx.beginPath(); // 开始一个新的路径
  ctx.arc(x, y, radius, startAngle, endAngle);
  ctx.closePath(); // 结束一个新的路径
  ctx.fillStyle = ball.color// 颜色
  ctx.fill(); // 填充
}

4、让球动起来

这是我们已经绘制了一个球,接下来根据球在x轴与y轴上的速度移动起来,并给y轴加上重力加速度,这里需要注意的是每次绘制都需要重新清除一下画布,canvas动画本质就是每次都清除一遍画布重新绘画

window.onload = () => {
    // .....
     
    let ball = {
            x0// 当前x轴坐标
            y0// 当前y轴坐标
            radius10// 半径
            g0.1// 重力加速度
            vx8// x轴移动速度
            vy4// y轴移动速度
            color"blue"// 颜色
          };

      setInterval(() => {
        // 先将之前绘制的擦除
        ctx.clearRect(00, ctx.canvas.width, ctx.canvas.height);
        // 绘制边框
        renderBorder(ctx);
        // 绘制球
        renderBall(ctx, ball);
        ball = updateBall(ball);
      }, 20);
  }


// 更新ball
function updateBall(ball) {
  ball.x += ball.vx;
  ball.y += ball.vy;
  ball.vy += ball.g;
  return ball;
}

5、优化

现在小球已经可以动起来了,再优化一下updateBall函数,真实一点,例如当小球调到边框的时候可以反弹回来,且每次反弹都会让加速度变慢


function updateBall(ctx, ball) {
      const width = ctx.canvas.width;
      const height = ctx.canvas.height;
      ball.x += ball.vx;
      ball.y += ball.vy;
      ball.vy += ball.g;
      if (ball.y + ball.radius >= height) {
        ball.y = height - ball.radius;
        ball.vy = -ball.vy * 0.5;
      }
      if (ball.y + ball.radius <= 0) {
        ball.y = 0 - ball.radius;
        ball.vy = -ball.vy * 0.5;
      }
      if (ball.x + ball.radius >= width) {
        ball.x = width - ball.radius;
        ball.vx = -ball.vx * 0.5;
      }
      if (ball.x + ball.radius <= 0) {
        ball.x = 0 - ball.radius;
        ball.vx = -ball.vx * 0.5;
      }
      return ball;
    }

整体代码


<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas小球</title>
  </head>
  <body>
    <!-- 新建canvas元素 -->
    <canvas id="canvas"></canvas>
  </body>
  <script>
    window.onload = () => {
      // 获取canvasDom
      const canvas = document.getElementById("canvas");
      // 手动设置宽高
      canvas.width = 800;
      canvas.height = 800;
      // 获取canvas上下文
      const ctx = canvas.getContext("2d");
      let ball = {
        x0// 当前x轴坐标
        y0// 当前y轴坐标
        radius10// 半径
        g0.1// 重力加速度
        vx8// x轴移动速度
        vy4// y轴移动速度
        color"blue"// 颜色
      };
      setInterval(() => {
        // 先将之前绘制的擦除
        ctx.clearRect(00, ctx.canvas.width, ctx.canvas.height);
        // 绘制边框
        renderBorder(ctx);
        // 绘制球
        renderBall(ctx, ball);
        ball = updateBall(ctx, ball);
      }, 20);
    };
    // 绘制边框
    function renderBorder(ctx) {
      // 首先获取画布的宽高
      const width = ctx.canvas.width;
      const height = ctx.canvas.height;
      ctx.beginPath(); // 开始一个新的路径
      ctx.moveTo(00); // 将路径的起始点移动到左上角
      ctx.lineTo(width, 0); // 使用直线连接到右上角(并不绘制)
      ctx.lineTo(width, height); // ...右下角
      ctx.lineTo(0, height); // ...左下角
      ctx.closePath(); // 结束一个新的路径
      ctx.stroke(); // 绘制当前已知路径
    }
    // 绘制一个球
    function renderBall(ctx, ball) {
      const x = ball.x + ball.radius// 圆弧中心(圆心)的 x 轴坐标。
      const y = ball.y + ball.radius// 圆弧中心(圆心)的 y 轴坐标。
      const radius = ball.radius// 半径
      const startAngle = 0// 圆弧的起始点
      const endAngle = 2 * Math.PI// 圆弧的结束点
      ctx.beginPath(); // 开始一个新的路径
      ctx.arc(x, y, radius, startAngle, endAngle);
      ctx.closePath(); // 结束一个新的路径
      ctx.fillStyle = ball.color// 颜色
      ctx.fill(); // 填充
    }
    // 更新ball
    function updateBall(ctx, ball) {
      const width = ctx.canvas.width;
      const height = ctx.canvas.height;
      ball.x += ball.vx;
      ball.y += ball.vy;
      ball.vy += ball.g;
      if (ball.y + ball.radius >= height) {
        ball.y = height - ball.radius;
        ball.vy = -ball.vy * 0.5;
      }
      if (ball.y + ball.radius <= 0) {
        ball.y = 0 - ball.radius;
        ball.vy = -ball.vy * 0.5;
      }
      if (ball.x + ball.radius >= width) {
        ball.x = width - ball.radius;
        ball.vx = -ball.vx * 0.5;
      }
      if (ball.x + ball.radius <= 0) {
        ball.x = 0 - ball.radius;
        ball.vx = -ball.vx * 0.5;
      }
      return ball;
    }
  </script>
</html>



参考文档

百度百科:弧度

MDN:canvas

慕课网: liuyubobobo老师的canvas教程