HTML5 画布中的动画
在本章中,我们将在 HTML5 canvas
中创建动画。
动画是连续的图像,使人产生了运动的幻觉。 但是,动画不仅限于运动。 随时间改变对象的背景也被视为动画。
在 HTML5 canvas
中创建动画的函数有以下三个:
setInterval(function, delay)
setTimeut(function, delay)
requestAnimationFrame(callback)
setInterval()
函数每隔延迟毫秒重复执行一次传递的函数。 setTimeout()
以毫秒为单位执行指定的功能。 为了创建动画,从执行的函数中调用setTimeout()
。 requestAnimationFrame()
函数允许浏览器在下一次重绘之前调用指定的函数来更新动画。 浏览器进行了一些优化。
沿曲线移动
在第一个动画中,对象沿曲线移动。
move_along_curve.html
<!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()
函数使move_ball()
函数每DELAY
ms 调用一次。
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()
方法重绘画布。
淡出
淡出是改变对象状态的动画。 这是一种过渡动画。
fading_out.html
<!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
ms 之后,从其内部调用fadeout()
方法。 这种做法称为递归。
泡泡
以下示例受到 Java 2D 演示示例的启发。
bubbles.html
<!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 坐标以及半径之外,它还包含颜色值的属性。
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()
函数清除画布并绘制数组中的所有圆圈。
星空
下面的示例创建一个星空动画。
starfield.html
<!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
ms 之后调用自己,从而创建动画。
在 HTML5 画布教程的这一章中,我们介绍了动画。