HTML5 Canvas 中的动画
最后修改于 2023 年 7 月 17 日
在本章中,我们在 HTML5 Canvas 中创建动画。
动画 是连续快速播放的图像,给人以运动的错觉。然而,动画并不仅限于运动。随时间改变对象的背景也被认为是动画。
在 HTML5 Canvas 中创建动画有三个函数:
- setInterval(function, delay)
- setTimeut(function, delay)
- requestAnimationFrame(callback)
setInterval 函数每隔指定的毫秒数(delay)重复执行传入的函数。setTimeout 在指定的毫秒数(delay)后执行指定的函数。为了创建动画,setTimeout 会在其执行的函数内部调用。requestAnimationFrame 函数允许浏览器在下次重绘之前调用指定的函数来更新动画。浏览器会进行一些优化。
沿曲线移动
在第一个动画中,一个对象沿曲线移动。
<!DOCTYPE html>
<html>
<head>
<title>HTML5 canvas move along curve</title>
<style>
canvas { border: 1px solid #bbbbbb }
</style>
<script>
var canvas;
var ctx;
var x = 20;
var y = 80;
const DELAY = 30;
const RADIUS = 10;
function init() {
canvas = document.getElementById('myCanvas');
ctx = canvas.getContext('2d');
setInterval(move_ball, DELAY);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.fillStyle = "cadetblue";
ctx.arc(x, y, RADIUS, 0, 2*Math.PI);
ctx.fill();
}
function move_ball() {
x += 1;
if (x > canvas.width + RADIUS) {
x = 0;
}
y = Math.sin(x/32)*30 + 80;
draw();
}
</script>
</head>
<body onload="init();">
<canvas id="myCanvas" width="350" height="150">
</canvas>
</body>
</html>
该示例将一个圆沿正弦曲线移动。当圆移动到画布的右侧边界之外时,它会重新出现在左侧。
setInterval(move_ball, DELAY);
setInterval 函数会每隔 DELAY 毫秒调用一次 move_ball 函数。
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.fillStyle = "cadetblue";
ctx.arc(x, y, RADIUS, 0, 2*Math.PI);
ctx.fill();
}
draw 方法使用 clearRect 方法清除画布,并绘制一个具有更新后的 x 和 y 坐标的新圆。
function move_ball() {
x += 1;
if (x > canvas.width + RADIUS) {
x = 0;
}
y = Math.sin(x/32)*30 + 80;
draw();
}
在 move_ball 函数中,我们更新圆心点的 x 和 y 坐标。我们检查球是否已经超过画布的右边缘,然后调用 draw 方法重新绘制画布。
淡出
淡出是一种改变对象状态的动画。它是一种过渡动画。
<!DOCTYPE html>
<html>
<head>
<style>
canvas {border: 1px solid #bbbbbb}
</style>
<title>HTML5 canvas fading out</title>
<script>
var canvas;
var ctx;
var alpha = 1;
var rx = 20;
var ry = 20;
var rw = 120;
var rh = 80;
const DELAY = 20;
function init() {
canvas = document.getElementById('myCanvas');
ctx = canvas.getContext('2d');
canvas.addEventListener("click", onClicked);
ctx.fillRect(rx, ry, rw, rh)
}
function onClicked(e) {
var cx = e.x;
var cy = e.y;
if (cx >= rx && cx <= rx + rw &&
cy >= ry && cy <= ry + rh) {
fadeout();
}
}
function fadeout() {
if (alpha < 0) {
canvas.removeEventListener("click", onClicked);
ctx.globalAlpha = 1;
ctx.fillStyle = 'white';
ctx.fillRect(rx, ry, rw, rh);
return;
}
ctx.clearRect(rx, ry, rw, rh);
ctx.globalAlpha = alpha;
ctx.fillRect(rx, ry, rw, rh)
alpha -= 0.01;
setTimeout(fadeout, DELAY);
}
</script>
</head>
<body onload="init();">
<canvas id="myCanvas" width="350" height="250">
</canvas>
</body>
</html>
有一个矩形对象。当我们点击矩形时,它开始淡出。
canvas.addEventListener("click", onClicked);
使用 addEventListener 方法向画布添加了一个 click 监听器。收到鼠标点击后,会调用 onClicked 函数。
ctx.fillRect(rx, ry, rw, rh)
最初,画布上会绘制一个具有默认黑色填充的矩形。
function onClicked(e) {
var cx = e.x;
var cy = e.y;
if (cx >= rx && cx <= rx + rw &&
cy >= ry && cy <= ry + rh) {
fadeout();
}
}
在 onClicked 函数内部,我们确定鼠标点击的 x 和 y 坐标。我们将鼠标坐标与矩形的外部边界进行比较,如果它落在矩形的区域内,则调用 fadeout 方法。
if (alpha < 0) {
canvas.removeEventListener("click", onClicked);
ctx.globalAlpha = 1;
ctx.fillStyle = 'white';
ctx.fillRect(rx, ry, rw, rh);
return;
}
当矩形完全透明时,我们删除监听器,并用不透明的白色填充该区域。return 语句会结束 fadeout 函数的递归调用。
ctx.clearRect(rx, ry, rw, rh); ctx.globalAlpha = alpha; ctx.fillRect(rx, ry, rw, rh)
矩形的区域被清除,并以更新的 alpha 值填充。
alpha -= 0.01;
alpha 值会减少一小部分。
setTimeout(fadeout, DELAY);
在 DELAY 毫秒后,fadeout 方法会从其自身内部调用。这种做法称为递归。
气泡
以下示例灵感来自 Java 2D 演示。
<!DOCTYPE html>
<html>
<head>
<title>HTML5 canvas bubbles</title>
<style>
canvas {
border: 1px solid #bbb;
background: #000;
}
</style>
<script>
var cols = ["blue", "cadetblue", "green", "orange", "red", "yellow",
"gray", "white"];
const NUMBER_OF_CIRCLES = 35;
const DELAY = 30;
var maxSize;
var canvas;
var ctx;
var circles;
function Circle(x, y, r, c) {
this.x = x;
this.y = y;
this.r = r;
this.c = c;
}
function init() {
canvas = document.getElementById('myCanvas');
ctx = canvas.getContext('2d');
circles = new Array(NUMBER_OF_CIRCLES);
initCircles();
doStep();
}
function initCircles() {
var w = canvas.width;
var h = canvas.height;
maxSize = w / 10;
for (var i = 0; i < circles.length; i++) {
var rc = getRandomCoordinates();
var r = Math.floor(maxSize * Math.random());
var c = cols[Math.floor(Math.random()*cols.length)]
circles[i] = new Circle(rc[0], rc[1], r, c);
}
}
function doStep() {
for (var i = 0; i < circles.length; i++) {
var c = circles[i];
c.r += 1;
if (c.r > maxSize) {
var rc = getRandomCoordinates();
c.x = rc[0];
c.y = rc[1];
c.r = 1;
}
}
drawCircles();
setTimeout(doStep, DELAY);
}
function getRandomCoordinates() {
var w = canvas.width;
var h = canvas.height;
var x = Math.floor(Math.random() * (w - (maxSize / 2)));
var y = Math.floor(Math.random() * (h - (maxSize / 2)));
return [x, y];
}
function drawCircles() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < circles.length; i++) {
ctx.beginPath();
ctx.lineWidth = 2.5;
var c = circles[i];
ctx.strokeStyle = c.c;
ctx.arc(c.x, c.y, c.r, 0, 2*Math.PI);
ctx.stroke();
}
}
</script>
</head>
<body onload="init();">
<canvas id="myCanvas" width="350" height="250">
</canvas>
</body>
</html>
在此示例中,屏幕上会随机出现和消失不断变大的彩色气泡。
var cols = ["blue", "cadetblue", "green", "orange", "red", "yellow",
"gray", "white"];
这些颜色用于绘制气泡。
function Circle(x, y, r, c) {
this.x = x;
this.y = y;
this.r = r;
this.c = c;
}
这是 Circle 对象的构造函数。除了 x 和 y 坐标以及半径之外,它还包含用于颜色值的 c 属性。
circles = new Array(NUMBER_OF_CIRCLES);
circles 数组用于保存圆对象。
for (var i = 0; i < circles.length; i++) {
var rc = getRandomCoordinates();
var r = Math.floor(maxSize * Math.random());
var c = cols[Math.floor(Math.random()*cols.length)]
circles[i] = new Circle(rc[0], rc[1], r, c);
}
circles 数组被填充了圆。我们计算随机坐标、随机初始半径和随机颜色值。
function doStep() {
doStep 代表程序的一个动画周期。
for (var i = 0; i < circles.length; i++) {
var c = circles[i];
c.r += 1;
if (c.r > maxSize) {
var rc = getRandomCoordinates();
c.x = rc[0];
c.y = rc[1];
c.r = 1;
}
}
我们遍历 circles 数组并增加每个圆的半径。当圆达到最大尺寸时,它会被随机重新定位并最小化。
setTimeout(doStep, DELAY);
使用 setTimeout 方法创建动画。您可能需要调整 DELAY 值以适应您的硬件。
function drawCircles() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < circles.length; i++) {
ctx.beginPath();
ctx.lineWidth = 2.5;
var c = circles[i];
ctx.strokeStyle = c.c;
ctx.arc(c.x, c.y, c.r, 0, 2*Math.PI);
ctx.stroke();
}
}
drawCircles 函数清除画布并绘制数组中的所有圆。
星空
以下示例创建了一个星空动画。
<!DOCTYPE html>
<html>
<head>
<title>HTML5 canvas star field</title>
<script>
var canvas_w;
var canvas_h;
var canvas;
var ctx;
var layer1;
var layer2;
var layer3;
const DELAY = 20;
const N_STARS = 60;
const SPEED1 = 3;
const SPEED2 = 2;
const SPEED3 = 1;
function init() {
canvas = document.getElementById("myCanvas");
ctx = canvas.getContext("2d");
canvas_w = canvas.width;
canvas_h = canvas.height;
layer1 = new layer(N_STARS, SPEED1, "#ffffff");
layer2 = new layer(N_STARS, SPEED2, "#dddddd");
layer3 = new layer(N_STARS, SPEED3, "#999999");
setTimeout("drawLayers()", DELAY);
}
function star() {
this.x = Math.floor(Math.random()*canvas_w);
this.y = Math.floor(Math.random()*canvas_h);
this.move = function(speed) {
this.y = this.y + speed;
if (this.y > canvas_h) {
this.y = 0;
this.x = Math.floor(Math.random()*canvas_w);
}
}
this.draw = function(col) {
ctx.fillStyle = col;
ctx.fillRect(this.x, this.y , 1, 1);
}
}
function layer(n, sp, col) {
this.n = n;
this.sp = sp;
this.col = col;
this.stars = new Array(this.n);
for (var i=0; i < this.n; i++) {
this.stars[i] = new star();
}
this.moveLayer = function() {
for (var i=0; i < this.n; i++) {
this.stars[i].move(this.sp);
}
}
this.drawLayer = function() {
for (var i=0; i < this.n; i++) {
this.stars[i].draw(this.col);
}
}
}
function drawLayers() {
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas_w, canvas_h);
layer1.moveLayer();
layer2.moveLayer();
layer3.moveLayer();
layer1.drawLayer();
layer2.drawLayer();
layer3.drawLayer();
setTimeout("drawLayers()", DELAY);
}
</script>
</head>
<body onload="init();">
<canvas id="myCanvas" width="800" height="600">
</canvas>
</body>
</html>
星空动画是通过创建三个不同的图层来实现的;每个图层包含具有不同速度和颜色阴影的星星(小点)。前景图层中的星星更亮、移动更快,后景中的星星更暗、移动更慢。
layer1 = new layer(N_STARS, SPEED1, "#ffffff"); layer2 = new layer(N_STARS, SPEED2, "#dddddd"); layer3 = new layer(N_STARS, SPEED3, "#999999");
创建了三个星星图层。它们具有不同的速度和颜色阴影。
function star() {
this.x = Math.floor(Math.random()*canvas_w);
this.y = Math.floor(Math.random()*canvas_h);
...
创建星星时,会为其赋予随机坐标。
this.move = function(speed) {
this.y = this.y + speed;
if (this.y > canvas_h) {
this.y = 0;
this.x = Math.floor(Math.random()*canvas_w);
}
}
move 方法移动星星;它会增加其 y 坐标。
this.draw = function(col) {
ctx.fillStyle = col;
ctx.fillRect(this.x, this.y , 1, 1);
}
draw 方法在画布上绘制星星。它使用 fillRect 方法以给定的颜色阴影绘制一个小矩形。
function layer(n, sp, col) {
this.n = n;
this.sp = sp;
this.col = col;
this.stars = new Array(this.n);
...
图层是具有给定速度和颜色阴影的 n 个星星的集合。星星存储在 stars 数组中。
for (var i=0; i < this.n; i++) {
this.stars[i] = new star();
}
在创建图层时,stars 数组会被填充星对象。
this.moveLayer = function() {
for (var i=0; i < this.n; i++) {
this.stars[i].move(this.sp);
}
}
moveLayer 方法遍历星星数组并调用每个星星的 move 方法。
this.drawLayer = function() {
for (var i=0; i < this.n; i++) {
this.stars[i].draw(this.col);
}
}
同样,drawLayer 方法调用每个星星的 draw 方法。
function drawLayers() {
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas_w, canvas_h);
layer1.moveLayer();
layer2.moveLayer();
layer3.moveLayer();
layer1.drawLayer();
layer2.drawLayer();
layer3.drawLayer();
setTimeout("drawLayers()", DELAY);
}
drawLayers 函数移动每个图层的星星并在画布上绘制它们。它会在 DELAY 毫秒后调用自身,从而创建动画。
在本章的 HTML5 Canvas 教程中,我们介绍了动画。