# 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 画布教程的这一章中，我们介绍了动画。