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 教程中,我们介绍了动画。