zoukankan      html  css  js  c++  java
  • HTML5Canvas动画原理

    下面我将利用“矩形”作为一个特例来讲解:

    • JavaScript实现canvas动画的基本原理
    • 如何创建一个简单的动画控制器

    此外,我还写了一个此文的国际版本,较本文,其代码和样例比较多,且为英文撰写。

    一、在canvas上绘制一个矩形

    首先,通过var ctx = document.getElementById(“canvas”).getContext(“2d”)获取canvas的2d上下文对象,可以把ctx看做是一只神奇的画笔,如果你想绘制一个实体矩形,那么调用它的fillRect(x,y,width,height)方法,如果你想绘制一条直线,可以调用lineTo(x, y)……

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
        var context = document.getElementById("canvas").getContext("2d");
     
        //Set location coordinates
        var x = 100; 
        var y = 50;
     
        //Set size
        var width = 50;
        var height = 80;
                
        //Set colors
        var backColor = "Red";
        var edgeColor = "Black";
     
        ctx.fillStyle = backColor;  //Set backColor as pen color 
        ctx.fillRect(x,y,width,height); //Draws a filled rectangle
        ctx.strokeStyle = edgeColor; //Set edgeColor as pen color
        ctx.strokeRect(x,y,width,height); //Draws a rectangular outline

    二、构建矩形类

    在一个动画中,矩形肯定不止有一个。如果想绘制多个矩形,而且它们各自有不同的样式,那么最好的方法就是:将矩形抽象为一个类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
        Rectangle = function(cfg)  //cfg is a object of customize parameters
        {
            //Set default parameters
            this.width = 50;   
            this.height = 80;       
            this.x = 100;    
            this.y = 50;
            this.backColor = "Red";  
            this.edgeColor = "Black"; 
     
            //Set customize parameters
            this.setArguments(cfg);  
        }
        Rectangle.prototype.setArguments = function(cfg) //Set customize parameters
        {
            for(var x in cfg)
                this[x] = cfg[x];
        }
        Rectangle.prototype.draw = function() //Draw method
        {
            ctx.fillStyle = this.backColor;  //Set backColor as pen color 
            ctx.fillRect(this.x,this.y,this.width,this.height); //Draws a filled rectangle
            ctx.strokeStyle = this.edgeColor; //Set edgeColor as pen color
            ctx.strokeRect(this.x,this.y,this.width,this.height); //Draws a rectangular outline
        }

    三、移动一个矩形

    很可惜ctx只能绘制静态的图形。所以需要我们自己编码实现动画效果。先看看下面这两幅图片:


    第二张动态的图片是由4张静态图片组成的,由于人眼的视觉残留效应.所以当多张静态的图片快速切换时,我们便看到了动画。将此原理代码化,来实现矩形的移动动画。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
        function move()
        {
            moveShape.animationStatus["Move"] = "new";
            timer = setInterval(function()   //Run once every 24ms
            {   
                if(moveShape.animationStatus["Move"] == "new")
                {                   
                    ctx.clearRect(0,0,600,400);  //Clear the canvas
                    moveShape.nextPosition();   //Set new (x,y) 
                    moveShape.draw();   //Draw the rectangle
                }
                else
                    clearInterval(timer);  //Stop setInterval() when it arrives
            }, 24);
        }

    四、丢失的红色矩形

    根据之前的三步内容,我编写了一个Demo:

    在黄色矩形移动时,红色矩形不见了,问题出在哪呢?回到之前第三步代码的第10行moveShape.draw(),由于重绘canvas时,只调用了黄色矩形的draw(),红色矩形自然就消失了。正确的做法是:在第10行后增加一行代码调用红色矩形的draw(),但是这并不是最好的解决方法。如果canvas上有100个矩形呢?难道要增加100行?比较好的方法是:将画板上的所有图形保存到一个ShapeOnCanvas数组里,把第10行代码替换成:

    1
    2
        for(var i=0;i<ShapeOnCanvas.length;i++)  
                ShapeOnCanvas[i].draw();  //Draw all shapes that on canvas

    五、构建动画控制器

    通常的情况是,在同一时刻,有些图形在移动、有些图形在淡入、有些图形在旋转……人都是懒惰的,所以我的目标是一行代码就能实现这些功能,当然这行代码肯定是一个函数调用的接口。

    函数调用接口:cmd ({ a1,b1,c1, a2,b2,c2, a3,b3,c3, … });

    • a* is a string such as “Move”,”Draw”
    • b* is an object of shape
    • c* is an object of animation parameters such as {aim_x:10,aim_y:10,moveSpeed:2}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
        cmd = function()
        {
            command = arguments;  //"command" is a long array that save animation commands
            //"command[i]" is a string such as "Move","Draw"
            //"command[i+1]" is an object of shape
            //"command[i+2]" is an object of animation parameters such as {aim_x:10,aim_y:10,moveSpeed:2} 
            for(var i=0; i<command.length; i+=3)  //Do the preparation before start animation(refresh canvas)
            {
                command[i+1].animationStatus[command[i]] = "new";  //init all animation status as "new"
                command[i+1].setArguments(command[i+2]);  //Set animation parameters
                ShapeOnCanvas.push(command[i+1]);  //Push it into the stack
            }
        
            timer = setInterval(function()   //Run once every 24ms
            {
                var j = 0;
                var allStop = true;  //"allStop" is the flag of all animations have been stopped
                for(var j=0; j<command.length; j+=3)
                    if(command[j+1].animationStatus[command[j]] == "new")
                    {
                        switch(command[j])
                        {
                            case "Draw":
                                command[j+1].draw();
                                break;
                            case "Move":
                                command[j+1].nextPosition();
                                break;
                        }   
                        allStop = false;
                    }
                    ctx.clearRect(0,0,600,400);  //Clear the canvas
                    for(var i=0;i<ShapeOnCanvas.length;i++)
                        ShapeOnCanvas[i].draw();  //Draw all shapes that on canvas
                    if(allStop)
                        clearInterval(timer);  //Stop setInterval() when all animations have been stopped
            }, 24);
        }

    如果你想扩充其他动画效果,例如添加“旋转”,只需要在switch添加:

    1
    2
    3
        case "Rotate"
            command[j+1].rotate(); //rotate()应该是根据旋转参数设置图形新的坐标位置
            break;

    六、串行动画

    上一步的动画控制器只能处理并行的动画,现在我们把它扩展一下,实现串行动画。
    其最终效果是:如下代码能实现Demo中的动画效果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
        cmd("Setup");
        cmd
        (
            "Draw",staticShape,{},
            "Move",moveShape,{aim_x:400,aim_y:300,moveSpeed:3},
            "Move",fastMoveShape,{aim_x:100,aim_y:300,moveSpeed:6}
        );
        cmd("Move",staticShape,{aim_x:300,aim_y:100,moveSpeed:5});
        cmd("End");

    解决方案:创建一个cmdQueue队列,按照调用cmd()的顺序,将传入cmd()的参数(一条并行动画命令)入队,启动一个setInterval(),每隔10ms检测一下之前一条并行动画命令是否执行结束,如果结束,则从队列出队下一条动画命令去执行。

     
     
     
     
     
     
     
     
     
     
     
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
        cmd = function()
        {
            if(arguments[0] == "Setup")  //setup animation
            {
                this.cmdQueue = new Array();  //"cmdQueue" is a queue to save animation commands
                //init the rear and front of the cmdQueue                   
                this.rear = 0;
                this.front = 0;
                cmdRun = false;
     
                var me = this;
                cmdTimer = setInterval(function()  //run once every 10ms
                {
                    //All previous animation commands have been stopped and there are remaining animation commands
                    if(me.cmdRun == false && me.front < me.rear) 
                    {
                        if(me.cmdQueue[me.front][0] == "End")
                            clearInterval(cmdTimer);  //Stop cmdTimer
                        else
                        {
                            refresh(me.cmdQueue[me.front]);  //Run one animation command that dequeue from cmdQueue
                            me.front++;
                        }
                    }
                }, 10);
            }
            else
                this.cmdQueue[this.rear++] = arguments;  //Enqueue animation commands to cmdQueue
        }

        refresh = function(command)
        {
            cmdRun = true;                  
        
            for(var i=0; i<command.length; i+=3)  //Do the preparation before start animation(refresh canvas)
            {
                command[i+1].animationStatus[command[i]] = "new";  //init all animation status as "new"
                command[i+1].setArguments(command[i+2]);  //Set animation parameters
                ShapeOnCanvas.push(command[i+1]);  //Push it into the stack
            }
        
            timer = setInterval(function()   //Run once every 24ms
            {
                var j = 0;
                var allStop = true;  //"allStop" is the flag of all animations have been stopped
                for(var j=0; j<command.length; j+=3)
                if(command[j+1].animationStatus[command[j]] == "new")
                {
                    switch(command[j])
                    {
                        case "Draw":
                            command[j+1].draw();
                            break;
                        case "Move":
                            command[j+1].nextPosition();
                            break;
                    }   
                    allStop = false;
                }
                ctx.clearRect(0,0,600,400);  //Clear the canvas
                for(var i=0;i<ShapeOnCanvas.length;i++)
                    ShapeOnCanvas[i].draw();  //Draw all shapes that on canvas
                if(allStop)
                {
                    cmdRun = false;
                    clearInterval(timer);  //Stop setInterval() when all animations have been stopped
                }
            }, 24);
        }
  • 相关阅读:
    第2章 NIO入门
    Docker Compose命令详解
    网络层相关术语解释
    linux查看并发连接数
    带宽计算方法
    ELK日志分析平台搭建
    mysql查找json格式列的指定字段值
    Oracle性能优化
    修改hosts文件不需要重启的方法
    freeswitch的internal的profile无法启动
  • 原文地址:https://www.cnblogs.com/liuguanghuiyes/p/3039328.html
Copyright © 2011-2022 走看看