zoukankan      html  css  js  c++  java
  • 对html5中canvas的事件模拟及动画编程

    html5现在很火,但兼容性问题还是很严重,不比pc下的ie6的问题少,做过智能机html5开发的话,应该明白滴。

    html5中的canvas本身是一种像素画板,想实现动画,就必须自己重绘,这点和用dom来现实的动画是有区别的。

    用dom的话,你只需要一直改变dom的样式,动画就完成,而canvas中,则是擦出掉上次画的,画上现在的,重复这一过程。

    而且在dom中,可以给每个元素设置事件,而canvas内部则没有事件机制,需要自己去模拟。

    用OO的方式来写代码,会让代码看起来很清楚,维护方便,我喜欢这种方式,这里有两个类:

      Stage   舞台类,表示canvas这个舞台。

         有这些公开的接口,

          add(thing)    加入一个物件,thing必须是Thing类的实现类的实例。

          addEvent(type,handler) stage的事件,目前只支持onEnterFrame这个事件,也就是帧频事件,大约是(1000/60)毫秒执行一次

          redraw()   重绘所有的thing对象。

      Thing   舞台上所有物件的超类,一个抽象类,可以被放置到舞台中去的,不能直接实例化

            有两个抽象方法,必须实现,

          draw(canvas)   会传进来一个canvas画板对象,用于绘制Thing。

          isScope(x,y)    会传进来鼠标相对于canvas的位置,返回boolean,用于判断鼠标是否在thing的范围,true表示鼠标在范围内,false表示不在。

           有一个公开的方法,

          addEvent(type,handler)  给物件设置事件,实现了onclick,onmouseover,onmousemove,onmousedown,onmouseup,onmouseout等事件。

       在下面的代码中,实现了两个类,Ball和Liveball,Ball继承了Thing,并实现了draw和isScope方法,然后LvieBall有继承Ball,然后被加入到stage中。

     
    View Code
    void function(window,document){
        //继承工具函数
        function extend(subClass,supClass){
               var AbstractMethod = {},i;
               for(i in supClass.prototype){
                  if(supClass.prototype[i] === extend.AbstractMethod) AbstractMethod[i] = true;
               }
               for(i in subClass.prototype){
                  if(AbstractMethod[i] && AbstractMethod[i] ===extend.AbstractMethod) throw Error(i+" Method do not Implement!");
               }
               var fun = function(){},
                   prototype = subClass.prototype;
               fun.prototype = supClass.prototype;
               subClass.prototype = new fun();
               for(var i in prototype){
                   subClass.prototype[i] = prototype[i];
               }
               subClass.$supClass = supClass;
               subClass.prototype.$supClass = function(){
                   var supClass = arguments.callee.caller.$supClass;
                   if(typeof supClass == 'function'){
                        supClass.apply(this,arguments);
                        this.$supClass = supClass;
                   }
               };
               subClass.prototype.constructor = subClass;
               return subClass;
        }
        //返回翻转的数组
        function Reverse(arr){
            var darr = [],item;
            for(var i=Math.max(arr.length-1,0);i>=0;i--) darr.push(arr[i]);
            return darr;
        }
        //约定的抽象方法
        extend.AbstractMethod = function(){};
        //表示舞台,也就是canvas画布
        function Stoge(canvas){
            this.canvas = canvas;
            this.node = canvas.canvas;
            this.height = this.node.height;
            this.width = this.node.width;
            this.thing = [];
            this.models = [];
            this.fps = 1000/60;
            this.lasttime = new Date();
            this.onEnterFrame = [];
            if(typeof options=== 'object') this.setOption(options);
            this.init();
        }
        Stoge.prototype = {
            //初始化
            init:function(){
                this.BuildEvent();
                var self = this;
                setInterval(function(){
                    for(var i=0,len=self.onEnterFrame.length;i<len;i++){
                        typeof self.onEnterFrame[i] == 'function' && self.onEnterFrame[i].call(self);
                    }
                },this.fps);
                this.showFPS();
            },
            //添加一个Thing
            add:function(thing){
                if(!thing) return this;
                this.thing.push(thing);
                thing.setStoge(this);
                thing.draw(this.canvas);
            },
            //添加一个模型
            addModel:function(model){
                this.models.push(model);
            },
            //删除一个物件
            remove:function(thing){
                //todo
            },
            //重新绘制所有的thing
            redraw:function(){
                this.canvas.clearRect(0,0,this.width,this.height);
                var i;
                for(i=0,len=this.thing.length;i<len;i++){
                    this.thing[i].draw(this.canvas);
                }
                for(i=0,len=this.models.length;i<len;i++){
                    this.models[i].draw(this.canvas);
                }
            },
            //绑定事件及派发事件
            BuildEvent:function(){
                var stoge = this;
                this.node.addEventListener('click',function(event){
                    var pos = stoge.getMousePosition(event.clientX,event.clientY);
                    var thing = Reverse(stoge.thing);
                    for(var i=0,len=thing.length;i<len;i++){
                        if(thing[i].isScope(pos.x,pos.y)){
                            thing[i].onClick(pos);
                            break;
                        }
                    }
                },false);
                document.body.addEventListener('mousemove',function(event){
                    
                    var pos = stoge.getMousePosition(event.clientX,event.clientY);
                    var thing = Reverse(stoge.thing);
                    for(var i=0,len=thing.length;i<len;i++){
                        if(thing[i].isScope(pos.x,pos.y) && !thing[i].getMouseState() && 'onMouseOver' in thing[i]){
                            thing[i].onMouseOver(event,pos);
                            break;
                        }else if(!thing[i].isScope(pos.x,pos.y) && thing[i].getMouseState() && 'onMouseOut' in thing[i]){
                            thing[i].onMouseOut(event,pos);
                            break;
                        }
                    }
                    for(var i=0,len=thing.length;i<len;i++){
                        if((thing[i].isScope(pos.x,pos.y) || thing[i].isMouseDown)){
                            if('onMouseMove' in thing[i]){
                                thing[i].onMouseMove(event,pos);
                            }
                            thing[i].setMouseState(true,pos);
                        }else{
                            thing[i].setMouseState(false,pos);
                        }
                    }
                    
                },false);
                this.node.addEventListener('mouseout',function(event){
                    var pos = stoge.getMousePosition(event.clientX,event.clientY);
                    var thing = Reverse(stoge.thing);
                    for(var i=0,len=thing.length;i<len;i++){
                        var pos2 = thing[i].getMouseLastPos();
                        if(thing[i].isScope(pos2.x,pos2.y) && 'onMouseOut' in thing[i]){ 
                            thing[i].onMouseOut(pos);
                            break;
                        }
                    }
                },false);
                this.node.addEventListener('mouseover',function(event){
                    var pos = stoge.getMousePosition(event.clientX,event.clientY);
                    var thing = Reverse(stoge.thing);
                    for(var i=0,len=thing.length;i<len;i++){
                        if(thing[i].isScope(pos.x,pos.y) && 'onMouseOver' in thing[i]){
                            thing[i].onMouseOver(event,pos);
                            break;
                        }
                    }
                },false);
                this.node.addEventListener('mousedown',function(event){
                    var pos = stoge.getMousePosition(event.clientX,event.clientY);
                    var thing = Reverse(stoge.thing);
                    for(var i=0,len=thing.length;i<len;i++){
                        if(thing[i].isScope(pos.x,pos.y) && 'onMouseDown' in thing[i]){
                            thing[i].onMouseDown(event,pos);
                            break;
                        }
                    }
                },false);
                document.body.addEventListener('mouseup',function(event){
                    var pos = stoge.getMousePosition(event.clientX,event.clientY);
                    var thing = Reverse(stoge.thing);
                    for(var i=0,len=thing.length;i<len;i++){
                        if((thing[i].isScope(pos.x,pos.y) || thing[i].isMouseDown)&& 'onMouseUp' in thing[i] ){
                            thing[i].onMouseUp(event,pos);
                        }
                    }
                },false);
            },
            addEvent:function(type,handler){
                switch(type){
                    case 'onEnterFrame':
                        this.onEnterFrame.push(handler);
                        break;
                }
            },
            //获得canvas节点在页面中的位置
            getNodePosition:function(){
                var top = 0, left = 0,node = this.node;
                do{
                    top += node.offsetTop;
                    left += node.offsetLeft;
                }while(node = node.offsetParent);
                return {top:top,left:left};
            },
            //获得鼠标相对于canvas中的位置
            getMousePosition:function(clientX,clientY){
                //文档中鼠标的位置
                var mouseY = clientY + document.documentElement.scrollTop,
                    mouseX = clientX + document.documentElement.scrollLeft;
                //文档中元素的位置
                var canvasPos = this.getNodePosition();
                //鼠标在canvas中的位置
                var x = mouseX - canvasPos.left,
                    y = mouseY - canvasPos.top;
                return {x:x,y:y};
            },
            //测试一个thing的是不是碰到边界
            testOutBorder:function(thing){
                var isInBorder = true;
                if(thing.x+thing.width/2 > this.width){
                    isInBorder = false;
                }
                if(thing.x-thing.width/2 < 0){
                    isInBorder = false;
                }
                if(thing.y+thing.height/2 > this.height){
                    isInBorder = false;
                }
                if(thing.y-thing.height/2 < 0){
                    isInBorder = false;
                }
                return isInBorder;
            },
            //判断两个thing是否有碰撞
            testOverlap:function(one,two){
                var x = two.x - one.x,
                    y = two.y - one.y,
                    l = Math.sqrt(x*x+y*y),
                    angle,
                    radian;
                if(one.radius+two.radius >= l){
    
                    radian = Math.atan2(y,x);
                    var sin = Math.sin(radian);
                    var cos = Math.cos(radian);
                    var pos0 = {x:0,y:0};
                    
                    var pos1 = this.rotate(x,y,sin,cos,true);
                    var vel0 = this.rotate(one.ax,one.ay,sin,cos,true);
                    var vel1 = this.rotate(two.ax,two.ay,sin,cos,true);
                    
                    
                    var vxTotal = vel0.x - vel1.x;
                    vel0.x = ((one.mass - two.mass) * vel0.x + 2 * one.mass * vel1.x)/(one.mass+two.mass);
                
                    vel1.x = vxTotal + vel0.x;
                    var absV = Math.abs(vel0.x) + Math.abs(vel1.x);
                    var overlap = (one.radius+two.radius) - Math.abs(pos0.x-pos1.x);
                    
                    pos0.x += vel0.x / absV * overlap;
                    pos1.x += vel1.x / absV * overlap;
                    var pos0f = this.rotate(pos0.x,pos0.y,sin,cos,false);
                    var pos1f = this.rotate(pos1.x,pos1.y,sin,cos,false);
                    two.x = one.x + pos1f.x;
                    two.y = one.y + pos1f.y;
                    one.x = one.x + pos0f.x;
                    one.y = one.y + pos0f.y;
    
                    var vel0f = this.rotate(vel0.x,vel0.y,sin,cos,false);
                    var vel1f = this.rotate(vel1.x,vel1.y,sin,cos,false);
                    one.ax = vel0f.x;
                    one.ay = vel0f.y;
                    two.ax = vel1f.x;
                    two.ay = vel1f.y;
                }
            },
            
            //获得改变角度后的位置
            rotate:function(x,y,sin,cos,reverse){
                var result = {};
                if(reverse){
                    result.x = x * cos + y * sin;
                    result.y = y * cos - x * sin;
                }else{
                    result.x = x * cos - y * sin;
                    result.y = y * cos + x * sin;
                }
                return result;
            },
            showFPS:function(){
                var self = this;
                var fps = {
                    draw:function(canvas){
                        var now = new Date();
                        canvas.save();
                        canvas.fillStyle = "#ccc";
                        canvas.font = "15px 微软雅黑";
                        canvas.fillText("拽拽小球看看", 10,20);
                        canvas.restore();
                        self.lasttime = new Date();
                    }
                };
                
                this.addModel(fps);
            }
        };
        //物品抽象类
        function Thing(x,y){
            this.stoge = null;
            this.x = x;
            this.y = y;
            this.vx = x;
            this.vy = y;
            this.width = null;
            this.height = null;
            this.MouseLastState = false;
            this.MouseLastPos = null;
            this.isMouseDown = false;
            this.onClickList = [];
            this.onMouseOverList = [];
            this.onMouseOutList = [];
            this.onMouseMoveList = [];
            this.onMouseDownList = [];
            this.onMouseUpList = [];
        }
        Thing.prototype = {
            //抽象方法
            drow:extend.AbstractMethod,
            isScope:extend.AbstractMethod,
            addEvent:function(type,EventHander){
                type = type.toLowerCase();
                switch(type){
                    case 'click':
                        this.onClickList.push(EventHander);
                    break;
                    case 'mouseover':
                        this.onMouseOverList.push(EventHander);
                    break;
                    case 'mouseout':
                        this.onMouseOutList.push(EventHander);
                    break;
                    case 'mousemove':
                        this.onMouseMoveList.push(EventHander);
                    break;
                    case 'mousedown':
                        this.onMouseDownList.push(EventHander);
                    break;
                    case 'mouseup':
                        this.onMouseUpList.push(EventHander);
                    break;
                }
            },
            onClick:function(event,pos){
                for(var i=0,len=this.onClickList.length;i<len;i++){
                    this.onClickList[i].call(this,event,pos);
                }
            },
            onMouseOver:function(event,pos){
                for(var i=0,len=this.onMouseOverList.length;i<len;i++){
                    this.onMouseOverList[i].call(this,event,pos);
                }
            },
            onMouseOut:function(event,pos){
                for(var i=0,len=this.onMouseOutList.length;i<len;i++){
                    this.onMouseOutList[i].call(this,event,pos);
                }
            },
            onMouseMove:function(event,pos){
                for(var i=0,len=this.onMouseMoveList.length;i<len;i++){
                    this.onMouseMoveList[i].call(this,event,pos);
                }
            },
            onMouseDown:function(event,pos){
                this.isMouseDown = true;
                for(var i=0,len=this.onMouseDownList.length;i<len;i++){
                    this.onMouseDownList[i].call(this,event,pos);
                }
            },
            onMouseUp:function(event,pos){
                if(!this.isMouseDown) return;
                this.isMouseDown = false;
                for(var i=0,len=this.onMouseUpList.length;i<len;i++){
                    this.onMouseUpList[i].call(this,event,pos);
                }
            },
            setMouseState:function(isIn,pos){
                this.MouseLastState = isIn;
                this.MouseLastPos = pos;
            },
            getMouseState:function(){
                return this.MouseLastState;
            },
            getMouseLastPos:function(){
                return this.MouseLastPos;
            },
            setStoge:function(stoge){
                this.stoge = stoge;
            }
        }
        function Ball(x,y,radius,strokeStyle,fillStyle){
            this.$supClass(x,y);
            radius = radius || 10;
            this.radius = radius;
            this.strokeStyle = strokeStyle;
            this.fillStyle = fillStyle;
            
        }
        Ball.prototype = {
            draw:function(canvas){
                canvas.save();
                if(this.strokeStyle)canvas.strokeStyle = this.strokeStyle;
                if(this.fillStyle)canvas.fillStyle = this.fillStyle;
                canvas.beginPath();
                canvas.arc(this.x,this.y,this.radius,0,6);
                canvas.closePath();
                if(this.strokeStyle)canvas.stroke();
                if(this.fillStyle)canvas.fill();
                canvas.restore();
                this.width = this.radius*2;
                this.height = this.radius*2;
            },
            isScope:function(x,y){
                var fx = this.x-x,
                    fy = this.y-y,
                    distance = Math.sqrt(Math.pow(fx,2)+Math.pow(fy,2));
                if(distance <= this.radius)
                    return true;
                else
                    return false;
            },
        };
        extend(Ball,Thing);
        
        function LiveBall(x,y,radius,mass,strokeStyle,fillStyle){
            this.$supClass(x,y,radius,strokeStyle,fillStyle);
            this.vy = 1.7;
            this.ax = 0;
            this.ay = 0;
            this.f = .8;
            this.mass = mass;
            this._isMouseDown = false;
            this.BuildEvent();
        }
        LiveBall.prototype = {
            BuildEvent:function(){
                this.addEvent('mousedown',function(event,pos){
                    clearTimeout(this._resource);
                    this._isMouseDown = true;
                    this._downX = pos.x - this.x;
                    this._downY = pos.y - this.y;
                    this._sx = pos.x;
                    this._sy = pos.y;
                });
                this.addEvent('mouseup',function(event,pos){
                    this._isMouseDown = false;
                    var dx = pos.x - this._sx,
                        dy = pos.y - this._sy,
                        di = Math.sqrt(dx*dx+dy*dy),
                        su = di/10;
                        this.ax = su * dx * this.f;
                        this.ay = su * dy * this.f;
                });
                this.addEvent('mousemove',function(event,pos){
                    if(this._isMouseDown){
                        clearTimeout(this._resource);
                        this.x = pos.x - this._downX;
                        this.y = pos.y - this._downY;
                        this.stoge.redraw();
                        var self = this;
                        this._resource = setTimeout(function(){
                            self._sx = pos.x;
                            self._sy = pos.y;
                        },10);
                    }
                });
            }
        };
        function ra(a,b){
            b = b || 10;
            a = a || 10;
            return parseInt((Math.random()*a)+b);
        }
        function rac(){
            return '#'+(parseInt(Math.random()*15)).toString(16)+(parseInt(Math.random()*15)).toString(16)+(parseInt(Math.random()*15)).toString(16);
        }
        extend(LiveBall,Ball);
        var dom =  document.getElementById('can');
        var ctx = dom.getContext('2d');
        var stoge = new Stoge(ctx);
        stoge.addEvent('onEnterFrame',function(){
                var one,two;
                for(var i=0,len=this.thing.length;i<len;i++){
                    one = this.thing[i];
                    if(!one._isMouseDown){
                        one.x += one.ax * 0.07;
                        one.ay += one.vy;
                        one.y += one.ay * 0.07;
                        if(one.y+one.radius >= this.height){
                            one.y = this.height - one.radius;
                            one.ay = -(one.ay * one.f);
                        }
                        if(one.y-one.radius <= 0){
                            one.y = one.radius;
                            one.ay = -(one.ay * one.f);
                        }
                        if(one.x+one.radius >= this.width){
                            one.x = this.width - one.radius;
                            one.ax = -(one.ax * one.f);
                        }
                        if(one.x-one.radius <= 0){
                            one.x = one.radius;
                            one.ax = -(one.ax * one.f);
                        }
                    }
                    for(var t=0,tlen=this.thing.length;t<tlen;t++){
                        two = this.thing[t];
                        one !== two && this.testOverlap(one,two);
                    }
                }
                this.redraw();
            });
        for(var i=0;i<10;i++){
            var radius = ra(20,20);
            var mass = (radius/40) * 20;
            var th = new LiveBall(ra(700,1),ra(100,1),radius,mass,null,rac());
            th.ax = ra(0,0);
            th.ay = ra(0,0);
            stoge.add(th);
    
        }
    
    }(window,document);
  • 相关阅读:
    POJ 2513 Colored Sticks 字典树 + 并查集 + 欧拉通路
    管理收尾往往是项目经理经常忽略的过程
    一个十人开发团队的人员安排
    GridView的RowCreated与RowDataBound事件区别
    全局程序集缓存(GAC)
    Aspose.Cells 使用整理
    Difference between Assembly and Namespace
    Ltib 应用初步
    setsocketopt() usage
    ARM地址空间
  • 原文地址:https://www.cnblogs.com/xingzhi/p/2801311.html
Copyright © 2011-2022 走看看