跳转至

HTML5 画布中的动画

原文: 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

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



回到顶部