如何在不留下痕迹的情况下为 JavaScript/Canvas 中的矩形制作动画?

How do I animate a rectangle in JavaScript/Canvas without leaving a trail?

我正在尝试使用 javascript/canvas 创建一个矩形在屏幕上向下移动的动画。

我遇到的问题是它移动了矩形,但在它后面留下了一条轨迹,而不是仅仅移动了矩形。 (fiddle: https://jsfiddle.net/winterswebs/hmgfwp9z/116/)

如何在不留下痕迹的情况下为对象设置动画?

let v = {
  movingObjects: [{
    path: new Path2D(),
    y: 0,
    img: {
      width: 100,
      height: 100,
    }
  }],
  animationTime: 0,
  lastFrameTime: 0,
  testingCountOut: 0,
}

const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");

canvas.width = 400;
canvas.height = 400;

function loop() {

  //if (v.testingCountOut >= 10) return;
  let now = Date.now();
  let delta = v.lastFrameTime == 0 ? 0 : (now - v.lastFrameTime) / 1000;
  v.lastFrameTime = now;
  draw(delta);
  //v.testingCountOut++;
  requestAnimationFrame(loop);
}

function draw(delta) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  v.animationTime = Number(v.animationTime) + Number(delta);
  let movingObjVY = 100;
  let movingObjYPos = v.animationTime * movingObjVY;

  v.movingObjects.forEach((element, index) => {
    ctx.save();
    ctx.beginPath();
    v.movingObjects[index].path.rect(
      10,
      element.y + movingObjYPos,
      element.img.width,
      element.img.height
    );
    ctx.fillStyle = "rgba(255,255,255,0.5)";
    ctx.fill(v.movingObjects[index].path);
    //ctx.strokeStyle = "rgba(0,0,0,1)";
    //ctx.stroke(v.movingObjects[index].path);
    ctx.restore();
  });
}

loop();
body {
  background-color: #777;
}

#canvas {
  border: 1px solid #000;
}
<canvas id="canvas"></canvas>

每次调用 v.movingObjects[index].path.rect(); 时都会向路径中添加一个新矩形,这就是为什么您还会看到所有以前的矩形的原因。您在这里有两个选择:要么在每次将矩形添加到路径时创建一个新路径,要么摆脱路径并使用 CanvasRenderingContext2D.fillRect() 函数简单地在 canvas 上绘制一个矩形。

ctx.fillRect() 文档:https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillRect

选项 1(重置 Path2D):

let v = {
  movingObjects: [{
    path: new Path2D(),
    y: 0,
    img: {
      width: 100,
      height: 100,
    }
  }],
  animationTime: 0,
  lastFrameTime: 0,
  testingCountOut: 0,
}

const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");

canvas.width = 400;
canvas.height = 400;

function loop() {

  //if (v.testingCountOut >= 10) return;
  let now = Date.now();
  let delta = v.lastFrameTime == 0 ? 0 : (now - v.lastFrameTime) / 1000;
  v.lastFrameTime = now;
  draw(delta);
  //v.testingCountOut++;
  requestAnimationFrame(loop);
}

function draw(delta) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  v.animationTime = Number(v.animationTime) + Number(delta);
  let movingObjVY = 100;
  let movingObjYPos = v.animationTime * movingObjVY;

  v.movingObjects.forEach((element, index) => {
    ctx.save();
    ctx.beginPath();
    v.movingObjects[index].path.rect(
      10,
      element.y + movingObjYPos,
      element.img.width,
      element.img.height
    );
    ctx.fillStyle = "rgba(255,255,255,0.5)";
    ctx.fill(v.movingObjects[index].path);
    ctx.closePath();
    v.movingObjects[index].path = new Path2D();
    ctx.restore();
  });
}

loop();
body {
  background-color: #777;
}

#canvas {
  border: 1px solid #000;
}
<canvas id="canvas"></canvas>

选项 2 (fillRect):

let v = {
  movingObjects: [{
    y: 0,
    img: {
      width: 100,
      height: 100,
    }
  }],
  animationTime: 0,
  lastFrameTime: 0,
  testingCountOut: 0,
}

const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");

canvas.width = 400;
canvas.height = 400;

function loop() {

  //if (v.testingCountOut >= 10) return;
  let now = Date.now();
  let delta = v.lastFrameTime == 0 ? 0 : (now - v.lastFrameTime) / 1000;
  v.lastFrameTime = now;
  draw(delta);
  //v.testingCountOut++;
  requestAnimationFrame(loop);
}

function draw(delta) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  v.animationTime = Number(v.animationTime) + Number(delta);
  let movingObjVY = 100;
  let movingObjYPos = v.animationTime * movingObjVY;

  v.movingObjects.forEach((element, index) => {
    ctx.save();
    ctx.beginPath();
    ctx.fillStyle = "rgba(255,255,255,0.5)";
    ctx.fillRect(
      10,
      element.y + movingObjYPos,
      element.img.width,
      element.img.height
    );
    ctx.closePath()
    ctx.restore();
  });
}

loop();
body {
  background-color: #777;
}

#canvas {
  border: 1px solid #000;
}
<canvas id="canvas"></canvas>

选项 3(在评论中提到):对动画使用翻译:在这里你一直有相同的路径,这就是为什么你只需要将矩形添加到路径一次。首先平移 canvas 然后绘制矩形。更多关于 CanvasRenderingContext2D.translate() 的信息:https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/translate)

let v = {
  movingObjects: [{
    path: new Path2D(),
    y: 0,
    img: {
      width: 100,
      height: 100,
    }
  }],
  animationTime: 0,
  lastFrameTime: 0,
  testingCountOut: 0,
}

const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");

canvas.width = 400;
canvas.height = 400;

function loop() {

  //if (v.testingCountOut >= 10) return;
  let now = Date.now();
  let delta = v.lastFrameTime == 0 ? 0 : (now - v.lastFrameTime) / 1000;
  v.lastFrameTime = now;
  draw(delta);
  //v.testingCountOut++;
  requestAnimationFrame(loop);
}

v.movingObjects.forEach((element, index) => {
    v.movingObjects[index].path.rect(
      10,
      element.y,
      element.img.width,
      element.img.height
    );
  });

function draw(delta) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  v.animationTime = Number(v.animationTime) + Number(delta);
  let movingObjVY = 100;
  let movingObjYPos = v.animationTime * movingObjVY;

  v.movingObjects.forEach((element, index) => {
    ctx.save();
    ctx.beginPath();
    ctx.translate(0, movingObjYPos)
    ctx.fillStyle = "rgba(255,255,255,0.5)";
    ctx.fill(v.movingObjects[index].path);
    ctx.closePath();
    ctx.restore();
  });
}

loop();
body {
  background-color: #777;
}

#canvas {
  border: 1px solid #000;
}
<canvas id="canvas"></canvas>