画布 API 的动画 - 第 3 部分:重力和动态渲染

在本系列的 部分 2中,我们创建了一颗球,当它与边界碰撞时会旋转屏幕并改变颜色。

牛仔板

由于我们将工作如此接近屏幕底部,我们应该隐藏任何有过流:隐藏的水平滚动栏,我们会稍微暗化它,以便稍微减少眼睛的燃烧。

 1[label index.html]
 2<!DOCTYPE html>
 3<html lang="en">
 4  <head>
 5    <meta charset="UTF-8"/>
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
 7    <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
 8    <title>HTML Canvas</title>
 9    <style>
10      body {
11        overflow: hidden;
12        background-color: #1a202c;
13      }
14    </style>
15  </head>
16  <body>
17
18    <canvas></canvas>
19
20  </body>
21  <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js"></script>
22  <script src="./canvas.js"></script>
23</html>
 1[label canvas.js]
 2// Utilities
 3const randomNum = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
 4const randomColor = colors => colors[Math.floor(Math.random() * colors.length)];
 5
 6// Get canvas element
 7const canvas = document.querySelector('canvas');
 8const c = canvas.getContext('2d');
 9
10// Make canvas fullscreen
11canvas.width = innerWidth;
12canvas.height = innerHeight;
13addEventListener('resize', () => {
14  canvas.width = innerWidth;
15  canvas.height = innerHeight;
16});
17
18// Control Panel
19const gui = new dat.GUI();
20
21const controls = {
22  count: 0,
23  velocity: 0,
24};
25
26gui.add(controls, 'dx', 0, 10);
27gui.add(controls, 'dy', 0, 10);
28
29// New Object
30class Ball {
31  constructor(x, y, radius, color) {
32    this.x = x;
33    this.y = y;
34    this.radius = radius;
35    this.color = color;
36  }
37};
38
39Ball.prototype.draw = function () {
40  c.beginPath();
41  c.fillStyle = this.color;
42  c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
43  c.fill();
44  c.closePath();
45};
46
47Ball.prototype.update = function () {
48  this.x += controls.dx;
49  this.y += -controls.dy;
50  this.draw();
51};
52
53const ball = new Ball(innerWidth / 2, innerHeight / 2, 50, 'red');
54
55// Handle changes
56const animate = () => {
57  requestAnimationFrame(animate);
58
59  c.clearRect(0, 0, canvas.width, canvas.height);
60
61  ball.update();
62};
63
64animate();

放下

我们只需要将每个下降的所有变量存储为对象,在屏幕上绘制一条线,并在运行更新时将某些值添加到y位置,以使其向下移动。

 1[label canvas.js]
 2class Drop {
 3  constructor(x, y, dy, thickness, length, color) {
 4    this.x = x;
 5    this.y = y;
 6    this.dy = dy;
 7    this.thickness = thickness;
 8    this.length = length;
 9    this.color = color;
10  }
11};
12
13Drop.prototype.draw = function () {
14  c.beginPath();
15  c.strokeStyle = this.color;
16  c.lineWidth = this.thickness;
17  c.moveTo(this.x, this.y);
18  c.lineTo(this.x, this.y - this.length);
19  c.stroke();
20  c.closePath();
21}
22
23Drop.prototype.update = function () {
24  this.y += this.dy;
25
26  this.draw();
27}

让我们将其中一个渲染到面板屏幕的中心,看看它是否工作。

 1const drop = new Drop(innerWidth / 2, innerHeight / 2, 2, 5, 30, 'red');
 2
 3const animate = () => {
 4  requestAnimationFrame(animate);
 5  c.clearRect(0, 0, canvas.width, canvas.height);
 6
 7  drop.update();
 8};
 9
10animate();

single drop

泰克

这是不错的,但我们将需要更多的渲染沿着顶部。 要有多个滴,我们可以只做一个数组,使用循环为每个项目分配随机值,然后使用forEach为每一个框更新它们。

要做到这一点,我们将创建一个计数器,每个框架都将以一个计数,每当它被某个数字完美分割时,我们将添加一个新的 drop 到数组中。

使用modulo运算符(%)我们可以分一个数字,并检查剩余的值是否等于0;因此,它越高,更少会出现新的下降。

虽然我们在这里,让我们给他们一些颜色. 我发现使用相同颜色的不同值,以及随机厚度和长度,有助于给出某种深度的幻觉。

 1[label canvas.js]
 2const colors = [ '#9C4AFF', '#8C43E6', '#7638C2', '#5E2C99', '#492378'];
 3
 4let drops = [];
 5let ticker = 0;
 6const animate = () => {
 7  requestAnimationFrame(animate);
 8  // Try using the 'residue' effect from Part 2
 9  // c.fillStyle = 'rgba(33, 33, 33, .3)'; //Lower opacity creates a longer tail
10  // c.fillRect(0, 0, canvas.width, canvas.height);
11  c.clearRect(0, 0, canvas.width, canvas.height);
12
13  drops.forEach((drop, index) => {
14    drop.update();
15    if (drop.y >= canvas.height) drops.splice(index, 1);
16  });
17
18  // Timing between drops
19  ticker++;
20  let count = controls.count === 0 ? 0 : randomNum(controls.count + 5, controls.count);
21  if (ticker % count == 0) {
22    const x = randomNum(0, innerWidth);
23    const y = 0;
24    const dy = controls.velocity === 0 ? 0 : randomNum(controls.velocity, controls.velocity + 10);
25    const thickness = randomNum(3, 5);
26    const length = randomNum(20, 50);
27
28    drops.push(new Drop(x, y, dy, thickness, length, randomColor(colors)));
29  };
30};

滴滴与重力

为了创建我们的喷射效果,当它们撞到地面时,我们将需要一些较小的粒子,这些粒子将具有重力类似的效果,从主要滴出发。

 1[label canvas.js]
 2class Droplet {
 3  constructor(x, y, dx, dy, radius, color) {
 4    this.x = x;
 5    this.y = y;
 6    this.dx = dx;
 7    this.dy = dy;
 8    this.radius = radius;
 9    this.color = color;
10    this.gravity = .1;
11  }
12};
13
14Droplet.prototype.draw = function () {
15  c.beginPath();
16  c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
17  c.fillStyle = this.color;
18  c.fill();
19  c.closePath();
20};

我們主要要擔心的是我們的重力。重力導致我們的向下運動增加,所以我們會想在「更新」方法上將這一點添加到我們的「dy」中。所以當我們生成許多滴滴向上移動時,有負的「dy」值,並將我們的重力值添加到它身上,每個框架就會放慢,逆轉方向,並加速,直到它被移除到我們的畫面上。

1Droplet.prototype.update = function () {
2  this.dy += this.gravity;
3  this.y += this.dy;
4  this.x += this.dx;
5
6  this.draw();
7};

我们将更新并删除它们,就像我们的主要滴一样。

1let droplets = [];
2const animate = () => {
3  droplets.forEach((droplet, index) => {
4    droplet.update();
5    if (droplet.y >= canvas.height) droplets.splice(index, 1);
6  });
7};

斯普拉斯

添加我们的粒子实际上是非常简单的,我们可以使用一个循环来生成它们的主要下降位置,并输入一些随机值为其余部分。

 1[label canvas.js]
 2class Drop {
 3  constructor(x, y, dy, thickness, length, color) {
 4    this.x = x;
 5    this.y = y;
 6    this.dy = dy;
 7    this.thickness = thickness;
 8    this.color = color;
 9    this.length = length;
10    this.gravity = .4;
11  }
12};
13
14Drop.prototype.update = function () {
15  // Stops drops if velocity controller is set to 0
16  if (this.dy > 0) this.dy += this.gravity;
17  this.y += this.dy;
18
19  // It runs splash over the whole length of the drop, to we'll narrow it down to the end.
20  if (this.y > canvas.height - 100) this.splash(this.x, this.y + (this.length * 2));
21
22  this.draw();
23}
24
25Drop.prototype.splash = function (x, y) {
26  for (let i = 0; i < 5; i++) {
27    const dx = randomNum(-3, 3);
28    const dy = randomNum(-1, -5);
29    const radius = randomNum(1, 3);
30
31    droplets.push(new Droplet(x, y, dx, dy, radius, randomColor(colors)));
32  };
33};

结论

虽然他仍然了解了大量的HTML画布,但希望这个短系列足够温和地介绍其可能性,大多数网站在很多方面都很相似,但创建自定义动画的能力提供了最流行的自动网站构建工具永远不会的独特性。

Published At
Categories with 技术
Tagged with
comments powered by Disqus