对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);

 

posted @ 2012-12-04 17:08  OD  阅读(9640)  评论(2编辑  收藏