原文: https://zetcode.com/gfx/html5canvas/animation/

在本章中,我们将在 HTML5 canvas中创建动画。

动画是连续的图像,使人产生了运动的幻觉。 但是,动画不仅限于运动。 随时间改变对象的背景也被视为动画。

在 HTML5 canvas中创建动画的函数有以下三个:

  • setInterval(function, delay)
  • setTimeut(function, delay)
  • requestAnimationFrame(callback)

setInterval()函数每隔延迟毫秒重复执行一次传递的函数。 setTimeout()以毫秒为单位执行指定的功能。 为了创建动画,从执行的函数中调用setTimeout()requestAnimationFrame()函数允许浏览器在下一次重绘之前调用指定的函数来更新动画。 浏览器进行了一些优化。

沿曲线移动

在第一个动画中,对象沿曲线移动。

move_along_curve.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>HTML5 canvas move along curve</title>
  5. <style>
  6. canvas { border: 1px solid #bbbbbb }
  7. </style>
  8. <script>
  9. var canvas;
  10. var ctx;
  11. var x = 20;
  12. var y = 80;
  13. const DELAY = 30;
  14. const RADIUS = 10;
  15. function init() {
  16. canvas = document.getElementById('myCanvas');
  17. ctx = canvas.getContext('2d');
  18. setInterval(move_ball, DELAY);
  19. }
  20. function draw() {
  21. ctx.clearRect(0, 0, canvas.width, canvas.height);
  22. ctx.beginPath();
  23. ctx.fillStyle = "cadetblue";
  24. ctx.arc(x, y, RADIUS, 0, 2*Math.PI);
  25. ctx.fill();
  26. }
  27. function move_ball() {
  28. x += 1;
  29. if (x > canvas.width + RADIUS) {
  30. x = 0;
  31. }
  32. y = Math.sin(x/32)*30 + 80;
  33. draw();
  34. }
  35. </script>
  36. </head>
  37. <body onload="init();">
  38. <canvas id="myCanvas" width="350" height="150">
  39. </canvas>
  40. </body>
  41. </html>

该示例沿正弦曲线移动一个圆。 圆圈移过画布的末端后,它再次出现在左侧。

  1. setInterval(move_ball, DELAY);

setInterval()函数使move_ball()函数每DELAY ms 调用一次。

  1. function draw() {
  2. ctx.clearRect(0, 0, canvas.width, canvas.height);
  3. ctx.beginPath();
  4. ctx.fillStyle = "cadetblue";
  5. ctx.arc(x, y, RADIUS, 0, 2*Math.PI);
  6. ctx.fill();
  7. }

draw()方法使用clearRect()方法清除画布,并绘制具有更新的 x 和 y 坐标的新圆。

  1. function move_ball() {
  2. x += 1;
  3. if (x > canvas.width + RADIUS) {
  4. x = 0;
  5. }
  6. y = Math.sin(x/32)*30 + 80;
  7. draw();
  8. }

move_ball()函数中,我们更新圆心的 x 和 y 坐标。 我们检查球是否已通过画布的右边缘,然后调用draw()方法重绘画布。

淡出

淡出是改变对象状态的动画。 这是一种过渡动画。

fading_out.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <style>
  5. canvas {border: 1px solid #bbbbbb}
  6. </style>
  7. <title>HTML5 canvas fading out</title>
  8. <script>
  9. var canvas;
  10. var ctx;
  11. var alpha = 1;
  12. var rx = 20;
  13. var ry = 20;
  14. var rw = 120;
  15. var rh = 80;
  16. const DELAY = 20;
  17. function init() {
  18. canvas = document.getElementById('myCanvas');
  19. ctx = canvas.getContext('2d');
  20. canvas.addEventListener("click", onClicked);
  21. ctx.fillRect(rx, ry, rw, rh)
  22. }
  23. function onClicked(e) {
  24. var cx = e.x;
  25. var cy = e.y;
  26. if (cx >= rx && cx <= rx + rw &&
  27. cy >= ry && cy <= ry + rh) {
  28. fadeout();
  29. }
  30. }
  31. function fadeout() {
  32. if (alpha < 0) {
  33. canvas.removeEventListener("click", onClicked);
  34. ctx.globalAlpha = 1;
  35. ctx.fillStyle = 'white';
  36. ctx.fillRect(rx, ry, rw, rh);
  37. return;
  38. }
  39. ctx.clearRect(rx, ry, rw, rh);
  40. ctx.globalAlpha = alpha;
  41. ctx.fillRect(rx, ry, rw, rh)
  42. alpha -= 0.01;
  43. setTimeout(fadeout, DELAY);
  44. }
  45. </script>
  46. </head>
  47. <body onload="init();">
  48. <canvas id="myCanvas" width="350" height="250">
  49. </canvas>
  50. </body>
  51. </html>

有一个矩形对象。 当我们单击矩形时,它开始淡出。

  1. canvas.addEventListener("click", onClicked);

通过addEventListener()方法将click监听器添加到画布。 再次单击鼠标后,将调用onClicked()函数。

  1. ctx.fillRect(rx, ry, rw, rh)

最初,在画布上以默认的黑色填充绘制了一个矩形。

  1. function onClicked(e) {
  2. var cx = e.x;
  3. var cy = e.y;
  4. if (cx >= rx && cx <= rx + rw &&
  5. cy >= ry && cy <= ry + rh) {
  6. fadeout();
  7. }
  8. }

onClicked()函数内,我们可以计算出鼠标单击的 x 和 y 坐标。 我们将鼠标坐标与矩形的外部边界进行比较,如果鼠标坐标落在矩形的区域内,则将调用fadeout()方法。

  1. if (alpha < 0) {
  2. canvas.removeEventListener("click", onClicked);
  3. ctx.globalAlpha = 1;
  4. ctx.fillStyle = 'white';
  5. ctx.fillRect(rx, ry, rw, rh);
  6. return;
  7. }

当矩形完全透明时,我们将移走监听器并以不透明的白色填充该区域。 return语句结束fadeout()函数的递归调用。

  1. ctx.clearRect(rx, ry, rw, rh);
  2. ctx.globalAlpha = alpha;
  3. ctx.fillRect(rx, ry, rw, rh)

矩形的区域被清除并填充有更新的 alpha 状态。

  1. alpha -= 0.01;

alpha值减小一小部分。

  1. setTimeout(fadeout, DELAY);

DELAY ms 之后,从其内部调用fadeout()方法。 这种做法称为递归。

泡泡

以下示例受到 Java 2D 演示示例的启发。

bubbles.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>HTML5 canvas bubbles</title>
  5. <style>
  6. canvas {
  7. border: 1px solid #bbb;
  8. background: #000;
  9. }
  10. </style>
  11. <script>
  12. var cols = ["blue", "cadetblue", "green", "orange", "red", "yellow",
  13. "gray", "white"];
  14. const NUMBER_OF_CIRCLES = 35;
  15. const DELAY = 30;
  16. var maxSize;
  17. var canvas;
  18. var ctx;
  19. var circles;
  20. function Circle(x, y, r, c) {
  21. this.x = x;
  22. this.y = y;
  23. this.r = r;
  24. this.c = c;
  25. }
  26. function init() {
  27. canvas = document.getElementById('myCanvas');
  28. ctx = canvas.getContext('2d');
  29. circles = new Array(NUMBER_OF_CIRCLES);
  30. initCircles();
  31. doStep();
  32. }
  33. function initCircles() {
  34. var w = canvas.width;
  35. var h = canvas.height;
  36. maxSize = w / 10;
  37. for (var i = 0; i < circles.length; i++) {
  38. var rc = getRandomCoordinates();
  39. var r = Math.floor(maxSize * Math.random());
  40. var c = cols[Math.floor(Math.random()*cols.length)]
  41. circles[i] = new Circle(rc[0], rc[1], r, c);
  42. }
  43. }
  44. function doStep() {
  45. for (var i = 0; i < circles.length; i++) {
  46. var c = circles[i];
  47. c.r += 1;
  48. if (c.r > maxSize) {
  49. var rc = getRandomCoordinates();
  50. c.x = rc[0];
  51. c.y = rc[1];
  52. c.r = 1;
  53. }
  54. }
  55. drawCircles();
  56. setTimeout(doStep, DELAY);
  57. }
  58. function getRandomCoordinates() {
  59. var w = canvas.width;
  60. var h = canvas.height;
  61. var x = Math.floor(Math.random() * (w - (maxSize / 2)));
  62. var y = Math.floor(Math.random() * (h - (maxSize / 2)));
  63. return [x, y];
  64. }
  65. function drawCircles() {
  66. ctx.clearRect(0, 0, canvas.width, canvas.height);
  67. for (var i = 0; i < circles.length; i++) {
  68. ctx.beginPath();
  69. ctx.lineWidth = 2.5;
  70. var c = circles[i];
  71. ctx.strokeStyle = c.c;
  72. ctx.arc(c.x, c.y, c.r, 0, 2*Math.PI);
  73. ctx.stroke();
  74. }
  75. }
  76. </script>
  77. </head>
  78. <body onload="init();">
  79. <canvas id="myCanvas" width="350" height="250">
  80. </canvas>
  81. </body>
  82. </html>

在该示例中,有越来越多的彩色气泡在屏幕上随机出现和消失。

  1. var cols = ["blue", "cadetblue", "green", "orange", "red", "yellow",
  2. "gray", "white"];

这些颜色用于绘制气泡。

  1. function Circle(x, y, r, c) {
  2. this.x = x;
  3. this.y = y;
  4. this.r = r;
  5. this.c = c;
  6. }

这是Circle对象的构造器。 除了 x 和 y 坐标以及半径之外,它还包含颜色值的属性。

  1. circles = new Array(NUMBER_OF_CIRCLES);

circles数组用于容纳圆形对象。

  1. for (var i = 0; i < circles.length; i++) {
  2. var rc = getRandomCoordinates();
  3. var r = Math.floor(maxSize * Math.random());
  4. var c = cols[Math.floor(Math.random()*cols.length)]
  5. circles[i] = new Circle(rc[0], rc[1], r, c);
  6. }

circles数组用圆圈填充。 我们计算随机坐标,随机初始半径和随机颜色值。

  1. function doStep() {

doStep()表示程序的动画周期。

  1. for (var i = 0; i < circles.length; i++) {
  2. var c = circles[i];
  3. c.r += 1;
  4. if (c.r > maxSize) {
  5. var rc = getRandomCoordinates();
  6. c.x = rc[0];
  7. c.y = rc[1];
  8. c.r = 1;
  9. }
  10. }

我们遍历circles数组并增加每个圆的半径。 当圆达到最大大小时,它会随机重新定位并最小化。

  1. setTimeout(doStep, DELAY);

setTimeout()方法用于创建动画。 您可能需要调整DELAY值以适合您的硬件。

  1. function drawCircles() {
  2. ctx.clearRect(0, 0, canvas.width, canvas.height);
  3. for (var i = 0; i < circles.length; i++) {
  4. ctx.beginPath();
  5. ctx.lineWidth = 2.5;
  6. var c = circles[i];
  7. ctx.strokeStyle = c.c;
  8. ctx.arc(c.x, c.y, c.r, 0, 2*Math.PI);
  9. ctx.stroke();
  10. }
  11. }

drawCircles()函数清除画布并绘制数组中的所有圆圈。

星空

下面的示例创建一个星空动画。

starfield.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>HTML5 canvas star field</title>
  5. <script>
  6. var canvas_w;
  7. var canvas_h;
  8. var canvas;
  9. var ctx;
  10. var layer1;
  11. var layer2;
  12. var layer3;
  13. const DELAY = 20;
  14. const N_STARS = 60;
  15. const SPEED1 = 3;
  16. const SPEED2 = 2;
  17. const SPEED3 = 1;
  18. function init() {
  19. canvas = document.getElementById("myCanvas");
  20. ctx = canvas.getContext("2d");
  21. canvas_w = canvas.width;
  22. canvas_h = canvas.height;
  23. layer1 = new layer(N_STARS, SPEED1, "#ffffff");
  24. layer2 = new layer(N_STARS, SPEED2, "#dddddd");
  25. layer3 = new layer(N_STARS, SPEED3, "#999999");
  26. setTimeout("drawLayers()", DELAY);
  27. }
  28. function star() {
  29. this.x = Math.floor(Math.random()*canvas_w);
  30. this.y = Math.floor(Math.random()*canvas_h);
  31. this.move = function(speed) {
  32. this.y = this.y + speed;
  33. if (this.y > canvas_h) {
  34. this.y = 0;
  35. this.x = Math.floor(Math.random()*canvas_w);
  36. }
  37. }
  38. this.draw = function(col) {
  39. ctx.fillStyle = col;
  40. ctx.fillRect(this.x, this.y , 1, 1);
  41. }
  42. }
  43. function layer(n, sp, col) {
  44. this.n = n;
  45. this.sp = sp;
  46. this.col = col;
  47. this.stars = new Array(this.n);
  48. for (var i=0; i < this.n; i++) {
  49. this.stars[i] = new star();
  50. }
  51. this.moveLayer = function() {
  52. for (var i=0; i < this.n; i++) {
  53. this.stars[i].move(this.sp);
  54. }
  55. }
  56. this.drawLayer = function() {
  57. for (var i=0; i < this.n; i++) {
  58. this.stars[i].draw(this.col);
  59. }
  60. }
  61. }
  62. function drawLayers() {
  63. ctx.fillStyle = '#000000';
  64. ctx.fillRect(0, 0, canvas_w, canvas_h);
  65. layer1.moveLayer();
  66. layer2.moveLayer();
  67. layer3.moveLayer();
  68. layer1.drawLayer();
  69. layer2.drawLayer();
  70. layer3.drawLayer();
  71. setTimeout("drawLayers()", DELAY);
  72. }
  73. </script>
  74. </head>
  75. <body onload="init();">
  76. <canvas id="myCanvas" width="800" height="600">
  77. </canvas>
  78. </body>
  79. </html>

通过产生三个不同的图层来创建星空动画。 每层由具有不同速度和颜色阴影的星星(小点)组成。 前层的星星更亮,移动速度更快,背面的星星更暗,移动速度更慢。

  1. layer1 = new layer(N_STARS, SPEED1, "#ffffff");
  2. layer2 = new layer(N_STARS, SPEED2, "#dddddd");
  3. layer3 = new layer(N_STARS, SPEED3, "#999999");

创建了三层星星。 它们具有不同的速度和颜色阴影。

  1. function star() {
  2. this.x = Math.floor(Math.random()*canvas_w);
  3. this.y = Math.floor(Math.random()*canvas_h);
  4. ...

创建星后,它会被赋予随机坐标。

  1. this.move = function(speed) {
  2. this.y = this.y + speed;
  3. if (this.y > canvas_h) {
  4. this.y = 0;
  5. this.x = Math.floor(Math.random()*canvas_w);
  6. }
  7. }

move()方法移动星星; 它增加了它的 y 坐标。

  1. this.draw = function(col) {
  2. ctx.fillStyle = col;
  3. ctx.fillRect(this.x, this.y , 1, 1);
  4. }

draw()方法在画布上绘制星星。 它使用fillRect()方法以给定的颜色绘制一个小矩形。

  1. function layer(n, sp, col) {
  2. this.n = n;
  3. this.sp = sp;
  4. this.col = col;
  5. this.stars = new Array(this.n);
  6. ...

一层是具有给定速度和颜色阴影的n星的集合。 星星存储在stars数组中。

  1. for (var i=0; i < this.n; i++) {
  2. this.stars[i] = new star();
  3. }

创建图层后,stars数组将填充星形对象。

  1. this.moveLayer = function() {
  2. for (var i=0; i < this.n; i++) {
  3. this.stars[i].move(this.sp);
  4. }
  5. }

moveLayer()方法遍历星星数组,并调用每个星星的move()方法。

  1. this.drawLayer = function() {
  2. for (var i=0; i < this.n; i++) {
  3. this.stars[i].draw(this.col);
  4. }
  5. }

同样,drawLayer()方法调用每个星星的draw()方法。

  1. function drawLayers() {
  2. ctx.fillStyle = '#000000';
  3. ctx.fillRect(0, 0, canvas_w, canvas_h);
  4. layer1.moveLayer();
  5. layer2.moveLayer();
  6. layer3.moveLayer();
  7. layer1.drawLayer();
  8. layer2.drawLayer();
  9. layer3.drawLayer();
  10. setTimeout("drawLayers()", DELAY);
  11. }

drawLayers()函数可移动每一层的星星并将其绘制在画布上。 它在DELAY ms 之后调用自己,从而创建动画。

在 HTML5 画布教程的这一章中,我们介绍了动画。