多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# HTML5 画布中的动画 > 原文: [https://zetcode.com/gfx/html5canvas/animation/](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` ```js <!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> ``` 该示例沿正弦曲线移动一个圆。 圆圈移过画布的末端后,它再次出现在左侧。 ```js setInterval(move_ball, DELAY); ``` `setInterval()`函数使`move_ball()`函数每`DELAY` ms 调用一次。 ```js 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 坐标的新圆。 ```js 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` ```js <!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> ``` 有一个矩形对象。 当我们单击矩形时,它开始淡出。 ```js canvas.addEventListener("click", onClicked); ``` 通过`addEventListener()`方法将`click`监听器添加到画布。 再次单击鼠标后,将调用`onClicked()`函数。 ```js ctx.fillRect(rx, ry, rw, rh) ``` 最初,在画布上以默认的黑色填充绘制了一个矩形。 ```js 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()`方法。 ```js if (alpha < 0) { canvas.removeEventListener("click", onClicked); ctx.globalAlpha = 1; ctx.fillStyle = 'white'; ctx.fillRect(rx, ry, rw, rh); return; } ``` 当矩形完全透明时,我们将移走监听器并以不透明的白色填充该区域。 `return`语句结束`fadeout()`函数的递归调用。 ```js ctx.clearRect(rx, ry, rw, rh); ctx.globalAlpha = alpha; ctx.fillRect(rx, ry, rw, rh) ``` 矩形的区域被清除并填充有更新的 alpha 状态。 ```js alpha -= 0.01; ``` `alpha`值减小一小部分。 ```js setTimeout(fadeout, DELAY); ``` 在`DELAY` ms 之后,从其内部调用`fadeout()`方法。 这种做法称为递归。 ## 泡泡 以下示例受到 Java 2D 演示示例的启发。 `bubbles.html` ```js <!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> ``` 在该示例中,有越来越多的彩色气泡在屏幕上随机出现和消失。 ```js var cols = ["blue", "cadetblue", "green", "orange", "red", "yellow", "gray", "white"]; ``` 这些颜色用于绘制气泡。 ```js function Circle(x, y, r, c) { this.x = x; this.y = y; this.r = r; this.c = c; } ``` 这是`Circle`对象的构造器。 除了 x 和 y 坐标以及半径之外,它还包含颜色值的属性。 ```js circles = new Array(NUMBER_OF_CIRCLES); ``` `circles`数组用于容纳圆形对象。 ```js 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`数组用圆圈填充。 我们计算随机坐标,随机初始半径和随机颜色值。 ```js function doStep() { ``` `doStep()`表示程序的动画周期。 ```js 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`数组并增加每个圆的半径。 当圆达到最大大小时,它会随机重新定位并最小化。 ```js setTimeout(doStep, DELAY); ``` `setTimeout()`方法用于创建动画。 您可能需要调整`DELAY`值以适合您的硬件。 ```js 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` ```js <!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> ``` 通过产生三个不同的图层来创建星空动画。 每层由具有不同速度和颜色阴影的星星(小点)组成。 前层的星星更亮,移动速度更快,背面的星星更暗,移动速度更慢。 ```js layer1 = new layer(N_STARS, SPEED1, "#ffffff"); layer2 = new layer(N_STARS, SPEED2, "#dddddd"); layer3 = new layer(N_STARS, SPEED3, "#999999"); ``` 创建了三层星星。 它们具有不同的速度和颜色阴影。 ```js function star() { this.x = Math.floor(Math.random()*canvas_w); this.y = Math.floor(Math.random()*canvas_h); ... ``` 创建星后,它会被赋予随机坐标。 ```js 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 坐标。 ```js this.draw = function(col) { ctx.fillStyle = col; ctx.fillRect(this.x, this.y , 1, 1); } ``` `draw()`方法在画布上绘制星星。 它使用`fillRect()`方法以给定的颜色绘制一个小矩形。 ```js function layer(n, sp, col) { this.n = n; this.sp = sp; this.col = col; this.stars = new Array(this.n); ... ``` 一层是具有给定速度和颜色阴影的`n`星的集合。 星星存储在`stars`数组中。 ```js for (var i=0; i < this.n; i++) { this.stars[i] = new star(); } ``` 创建图层后,`stars`数组将填充星形对象。 ```js this.moveLayer = function() { for (var i=0; i < this.n; i++) { this.stars[i].move(this.sp); } } ``` `moveLayer()`方法遍历星星数组,并调用每个星星的`move()`方法。 ```js this.drawLayer = function() { for (var i=0; i < this.n; i++) { this.stars[i].draw(this.col); } } ``` 同样,`drawLayer()`方法调用每个星星的`draw()`方法。 ```js 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 画布教程的这一章中,我们介绍了动画。