[ 登录注册 ]

HTML/CSS

基于HTML5的小球物理测试系统

2016-08-12 15:08:25 admin 返回上一页

你的浏览器不支持canvas,推荐使用chrome进行浏览。 半径: 
颜色: 
速度: 
弹性(0-1): 
入射角(0-360): 
起始X坐标(0-400): 
起始Y坐标(0-400): 
 

功能说明:

  一个基于HTML5 canvas的小球物理测试系统,用户可以手动为新的小球设置不同的属性值(颜色,半径,速度等),从而在canvas中发射小球,小球在运动过程中会收到重力,弹性以及摩擦力的影响。

实现原理:

在小球飞行过程中,以初始速度,入射角以及重力系数作为依据,正交分解得出小球X轴和Y轴上的分速度,通过定时器不断刷新canvas,显示出小球飞行的动画。当小球和墙壁产生碰撞时,以小球弹性为依据计算能量损耗,当小球在墙壁滚动时,以墙壁摩擦系数为依据计算其能量损耗。

代码分析:

var bounceWall = (function() {   

 return function(canvasId,backgroundColor) {   

this.init(canvasId, backgroundColor);   

}

})();

构造函数,其中调用了prototype中的init方法进行初始化。需要传入的参数是canvas的ID,和canvas的背景色,如果不传入backgroundColor参数,背景色默认为黑色。

bounceWall.prototype = (function() {   

  var CanvasSupport = Modernizr.canvas;

//检查浏览器是否支持canvas     var newBall = function(radius, color, speed, direction, currentX, currentY, elasticity) {       

this.radius = parseFloat(radius);                           

 //半径        this.color = color;                                         

//颜色        this.speed = parseFloat(speed);                             

 //速度        this.elasticity = parseFloat(elasticity);                   

//弹性         this.direction = parseFloat(direction);                     

 //入射角        this.currentX = parseFloat(currentX);                       

//初始X坐标        this.currentY = parseFloat(currentY);                       

 //初始Y坐标        this.dx = speed * Math.cos(this.direction * Math.PI / 180); 

 //计算其X轴方向的初始速度        this.dy = speed * Math.sin(this.direction * Math.PI / 180); 

//计算其Y轴方向的初始速度        this.nextX = this.currentX + this.dx;                       

 //根据速度和初速度得出其下次移动到的X坐标        this.nextY = this.currentY + this.dy;                       

 //根据速度和初速度得出其下次移动到的Y坐标    };     

开始进入到bounce wall的prototype,首先使用Modernizr检测是否可以使用canvas。Modernizr是一个可以用于检测浏览器是否支持一些新功能的js库,可以下载直接使用。

     之后出现的是小球的构造函数newBall,用户需要传入一系列的特性对其进行初始化,具体已经在注释中标出。需要特别注意的是其中的nextX和nextY记录的是小球下一次出现位置的坐标,它根据现在的位置(currentX和currentY)以及小球X轴和Y轴上的分速度(dx和dy)计算得出。nextX和nextY属性的用处主要是保证小球能和墙壁发生完全的碰撞,会在后面的代码分析。

    /* 绘制canvas的背景 */    var drawBackground = function(contextObj, backgroundColor, canvasWidth, canvasHeight) {       

contextObj.fillStyle = backgroundColor;       

 contextObj.fillRect(0, 0, parseInt(canvasWidth), parseInt(canvasHeight));   

 };

之后的函数是用户绘制canvas的背景,依据的属性是用户设定的背景色,canvas的宽度和高度。

 /* 更新小球状态 */   

var updateBallState = function(ballObj, canvasWidth, canvasHeight, gravityValue, friction) {       

 ballObj.currentX = ballObj.nextX;       

 ballObj.currentY = ballObj.nextY;       

ballObj.nextX = ballObj.currentX + ballObj.dx;       

ballObj.nextY = ballObj.currentY + ballObj.dy;       

/* 测试对墙壁产生是否X轴碰撞 */      

  if (ballObj.nextX < ballObj.radius) {           

ballObj.nextX = ballObj.radius;           

ballObj.dx = Math.max(0, (ballObj.dx + ((1 - ballObj.elasticity) * Math.abs(ballObj.dx))) * -1 - 1);       

 }       

else if ((ballObj.nextX + ballObj.radius) > parseInt(canvasWidth)) {           

 ballObj.nextX = parseInt(canvasWidth) - ballObj.radius;           

 ballObj.dx = Math.min(0, (ballObj.dx - ((1 - ballObj.elasticity) * Math.abs(ballObj.dx))) * -1 + 1);       

 }       

 /* 水平运动时受摩擦力影响 */       

 else if (ballObj.currentY >= (parseInt(canvasHeight) - ballObj.radius)) {          

  if (ballObj.dx > 0) {               

 ballObj.dx = Math.max(0, ballObj.dx - (ballObj.dx * friction) - 0.01);           

}          

  else if (ballObj.dx < 0) {               

 ballObj.dx = Math.min(0, ballObj.dx - (ballObj.dx * friction) + 0.01);           

 }      

  }      

  /* 测试对墙壁产生是否Y轴碰撞 */      

  if (ballObj.nextY < ballObj.radius) {           

ballObj.nextY = ballObj.radius;           

ballObj.dy = Math.max(0, (ballObj.dy + ((1 - ballObj.elasticity) * Math.abs(ballObj.dy))) * -1 - 1);       

 }       

 else if ((ballObj.nextY + ballObj.radius) > parseInt(canvasHeight)) {           

 ballObj.nextY = parseInt(canvasHeight) - ballObj.radius;           

ballObj.dy = Math.min(0, (ballObj.dy - ((1 - ballObj.elasticity) * Math.abs(ballObj.dy))) * -1 + 1);       

}      

  else {          

  ballObj.dy = ballObj.dy + gravityValue;       

}  

  };     

 接着是核心函数,updateBallState。该函数的作用是通过小球半径,canvas宽高等参数,判断小球是否和墙壁产生碰撞。并根据对四个不同墙壁的碰撞分别使用不同的处理方法。具体过程如下:

1.首先把上次记录的nextX和nextY设置为现在的currentX和currentY,更新了现在小球所处的位置。

2.计算小球的nextX和nextY,这两个参数将会决定小球下次所处的位置。

3.如果ballObj.nextX < ballObj.radius,册小球于左边的墙壁碰撞,此时把nextX设置为小球半径的值,是为了让小球在完全和墙壁发生碰撞(和墙壁距离为0)之后,再反弹。

      之后改变小球的速度:

ballObj.dx = Math.max(0, (ballObj.dx + ((1 - ballObj.elasticity) * Math.abs(ballObj.dx))) * -1 - 1);

      上面这个式子比较长,其中首先通过1 - ballObj.elasticity根据小球的弹性获取一个系数,该系数越大,则小球速度减小得越多。(1 - ballObj.elasticity) * Math.abs(ballObj.dx))) 则是在获取到刚刚的系数之后,再和小球现在的速度相乘,得到小球碰撞后减少的速度值。由此可以发现,小球弹性越好(ballObj.elasticity越大),则所减少的速度越少,每次碰撞性能损耗越少。之后再通过乘以-1把速度方向取反。可以发现最后还需要把得出的值减1,和0比较取其较大的值,这是因为小球在不断和墙壁碰撞的过程中,由于每次都是以现有速度的百分比作为减少的速度,所以永远都不会为0,小球永远都不会停下。所以这里通过减1,使小球的速度小于1时,速度会自动变成0,使小球最终停下。

4.如果不是和左边墙壁发生碰撞,再同理判断是否和其它墙壁碰撞,若产生碰撞,照例需要改变nextX和nextY,dx或dy的值。

    /* 把小球绘制在canvas中 */   

 var drawBallsToCanvas = function(ballArray, contextObj,

 backgroundColor, canvasWidth, canvasHeight, gravityValue, friction) {   

return function() {                  

  drawBackground(contextObj, backgroundColor, canvasWidth, canvasHeight);           

for (var i = 0, len = ballArray.length; i < len; i++) {              

  contextObj.beginPath(); debugger;              

  contextObj.fillStyle = ballArray[i].color;              

  contextObj.arc(ballArray[i].currentX, ballArray[i].currentY, ballArray[i].radius, 0, 2 * Math.PI, false);                contextObj.fill();              

  contextObj.closePath();               

updateBallState(ballArray[i], canvasWidth, canvasHeight, gravityValue, friction);                          

}       

}   

};     

这个方法主要功能是每次定时器触发的时候,重新把所有小球绘制到canvas上。具体操作是首先重新画canvas背景(否则在旧位置的小球会保留在canvas上并显示出来),然后遍历保存所有小球的数组,根据每个小球的属性,在canvas上画出具有新位置的小球,并调用之前的updateBallState方法,更新小球的属性,为下次的移动作准备。

  return {  

  /* 初始化函数 */       

 init: function(canvasId, backgroundColor, friction, gravityValue) {           

 if (!CanvasSupport)               

 return;          

  this.backgroundColor = backgroundColor || "#000";              

  //背景色,默认为黑色          

  this.friction = friction || 0.1;                                

 //墙壁摩擦系数,默认为0.1           

 this.gravityValue = gravityValue || 0.2;                        

 //重力系数,默认为0.2           

this.canvasObj = util.$(canvasId);                              

//canvas对象           

 this.canvasWidth = util.getComputedStyle(this.canvasObj).width; 

 //canvas高度           

 this.canvasHeight = util.getComputedStyle(this.canvasObj).height;

//canvas宽度            this.contextObj = this.canvasObj.getContext(2d);              

 //2d的context对象            this.ballArray = [];                                           

  //保存所有小球的数组           

 drawBackground(this.contextObj, this.backgroundColor, this.canvasWidth, this.canvasHeight);           

setInterval(drawBallsToCanvas(this.ballArray, this.contextObj, this.backgroundColor, this.canvasWidth, this.canvasHeight, this.gravityValue, this.friction = 0.1), 33);      

  },

最后进入到返回的object,它作为bounceWall的prototype。其中有init的方法,如果浏览器不支持canvas则返回,否则初始化一切初始属性,并启动setInternal定时器定时更新canvas的状态。

        /* 添加小球 */       

createBall: function(radius, color, speed, direction, currentX, currentY, elasticity)

{

// 小球半径 颜色 速度 方向          

  var ball = new newBall(radius, color, speed, direction, currentX, currentY, elasticity);           

this.ballArray.push(ball);       

}   

 }}

)();

添加小球的方法,demo中,该方法在“添加小球”的事件处理程序中被调用,根据用户输入数据进行新小球的添加。

   var wall = new bounceWall(wallCanvas, #000);    

//创建bounce wall    var radiusInput = util.$(radiusInput);            

//半径输入文本框    var color_Input = util.$(color_Input);           

  //颜色输入文本框    var speed_Input = util.$(speed_Input);            

//速度输入文本框    var elaticity_Input = util.$(elaticity_Input);   

  //弹性输入文本框    var inAngle_Input = util.$(inAngle_Input);       

  //入射角输入文本框    var X_Input = util.$(X_Input);                    

//初始X坐标输入文本框    var Y_Input = util.$(Y_Input);                    

 //初始Y坐标输入文本框    var btn = util.$(add_btn);                       

  //添加小球按钮    btn.onclick = function() {                         

  //绑定事件处理程序,调用creatBall方法创建新小球       

wall.createBall(radiusInput.value, color_Input.value, speed_Input.value, inAngle_Input.value, X_Input.value, Y_Input.value, elaticity_Input.value);         

  }

最后是demo中调用的代码。

完整demo代码:


View Code
*{margin:0; padding:0;}body{text-align:center;}ul{list-style-type:none;}#inputTable{width:200px; height:200px; margin:0 auto;}#inputTable input{width:50px;}View Code
<!doctype html><html lang="en"><head><meta charset="UTF-8"><title>Bounce Wall</title>    <link href="bounceWall.css" rel="stylesheet" type="text/css" /></head><body><canvas id="wallCanvas" width="400px" height="400px">Your browser does not support HTML5 Canvas.</canvas><table id="inputTable"><tr><td><label>半径:</label></td><td><input id="radiusInput" type="text" /></td></tr><tr><td><label>颜色:</label></td><td><input id="color_Input" type="text" /></td></tr><tr><td><label>速度:</label></td><td><input id="speed_Input" type="text" /></td></tr><tr><td><label>弹性(0-1):</label></td><td><input id="elaticity_Input" type="text" /></td></tr><tr><td><label>入射角(0-360):</label></td><td><input id="inAngle_Input" type="text" /></td></tr><tr><td><label>起始X坐标(0-600):</label></td><td><input id="X_Input" type="text" /></td></tr><tr><td><label>起始Y坐标(0-600):</label></td><td><input id="Y_Input" type="text" /></td></tr></table><input id="add_btn" type="button" value="添加小球" /></body><script src="util.js" type="text/javascript"></script><script src="modernizr.js" type="text/javascript"></script><script src="bounceWall.js" type="text/javascript"></script><script>    var wall = new bounceWall(wallCanvas, #000);     //创建bounce wall    var radiusInput = util.$(radiusInput);             //半径输入文本框    var color_Input = util.$(color_Input);             //颜色输入文本框    var speed_Input = util.$(speed_Input);             //速度输入文本框    var elaticity_Input = util.$(elaticity_Input);     //弹性输入文本框    var inAngle_Input = util.$(inAngle_Input);         //入射角输入文本框    var X_Input = util.$(X_Input);                     //初始X坐标输入文本框    var Y_Input = util.$(Y_Input);                     //初始Y坐标输入文本框    var btn = util.$(add_btn);                         //添加小球按钮    btn.onclick = function() {                           //绑定事件处理程序,调用creatBall方法创建新小球        wall.createBall(radiusInput.value, color_Input.value, speed_Input.value, inAngle_Input.value, X_Input.value, Y_Input.value, elaticity_Input.value);           }</script></html>View Code
var bounceWall = (function() {    return function(canvasId,backgroundColor) {    this.init(canvasId, backgroundColor);    }})();bounceWall.prototype = (function() {     var CanvasSupport = Modernizr.canvas; //检查浏览器是否支持canvas     var newBall = function(radius, color, speed, direction, currentX, currentY, elasticity) {        this.radius = parseFloat(radius);                            //半径        this.color = color;                                          //颜色        this.speed = parseFloat(speed);                              //速度        this.elasticity = parseFloat(elasticity);                    //弹性         this.direction = parseFloat(direction);                      //入射角        this.currentX = parseFloat(currentX);                        //初始X坐标        this.currentY = parseFloat(currentY);                        //初始Y坐标        this.dx = speed * Math.cos(this.direction * Math.PI / 180);  //计算其X轴方向的初始速度        this.dy = speed * Math.sin(this.direction * Math.PI / 180);  //计算其Y轴方向的初始速度        this.nextX = this.currentX + this.dx;                        //根据速度和初速度得出其下次移动到的X坐标        this.nextY = this.currentY + this.dy;                        //根据速度和初速度得出其下次移动到的Y坐标    };    /* 绘制canvas的背景 */    var drawBackground = function(contextObj, backgroundColor, canvasWidth, canvasHeight) {        contextObj.fillStyle = backgroundColor;        contextObj.fillRect(0, 0, parseInt(canvasWidth), parseInt(canvasHeight));    };        /* 更新小球状态 */    var updateBallState = function(ballObj, canvasWidth, canvasHeight, gravityValue, friction) {        ballObj.currentX = ballObj.nextX;        ballObj.currentY = ballObj.nextY;        ballObj.nextX = ballObj.currentX + ballObj.dx;        ballObj.nextY = ballObj.currentY + ballObj.dy;        /* 测试对墙壁产生是否X轴碰撞 */        if (ballObj.nextX < ballObj.radius) {            ballObj.nextX = ballObj.radius;            ballObj.dx = Math.max(0, (ballObj.dx + ((1 - ballObj.elasticity) * Math.abs(ballObj.dx))) * -1 - 1);        }        else if ((ballObj.nextX + ballObj.radius) > parseInt(canvasWidth)) {            ballObj.nextX = parseInt(canvasWidth) - ballObj.radius;            ballObj.dx = Math.min(0, (ballObj.dx - ((1 - ballObj.elasticity) * Math.abs(ballObj.dx))) * -1 + 1);        }        /* 水平运动时受摩擦力影响 */        else if (ballObj.currentY >= (parseInt(canvasHeight) - ballObj.radius)) {            if (ballObj.dx > 0) {                ballObj.dx = Math.max(0, ballObj.dx - (ballObj.dx * friction) - 0.01);            }            else if (ballObj.dx < 0) {                ballObj.dx = Math.min(0, ballObj.dx - (ballObj.dx * friction) + 0.01);            }        }        /* 测试对墙壁产生是否Y轴碰撞 */        if (ballObj.nextY < ballObj.radius) {            ballObj.nextY = ballObj.radius;            ballObj.dy = Math.max(0, (ballObj.dy + ((1 - ballObj.elasticity) * Math.abs(ballObj.dy))) * -1 - 1);        }        else if ((ballObj.nextY + ballObj.radius) > parseInt(canvasHeight)) {            ballObj.nextY = parseInt(canvasHeight) - ballObj.radius;            ballObj.dy = Math.min(0, (ballObj.dy - ((1 - ballObj.elasticity) * Math.abs(ballObj.dy))) * -1 + 1);        }        else {            ballObj.dy = ballObj.dy + gravityValue;        }    };       /* 把小球绘制在canvas中 */    var drawBallsToCanvas = function(ballArray, contextObj, backgroundColor, canvasWidth, canvasHeight, gravityValue, friction) {    return function() {                    drawBackground(contextObj, backgroundColor, canvasWidth, canvasHeight);            for (var i = 0, len = ballArray.length; i < len; i++) {                contextObj.beginPath(); debugger;                contextObj.fillStyle = ballArray[i].color;                contextObj.arc(ballArray[i].currentX, ballArray[i].currentY, ballArray[i].radius, 0, 2 * Math.PI, false);                contextObj.fill();                contextObj.closePath();                updateBallState(ballArray[i], canvasWidth, canvasHeight, gravityValue, friction);                           }        }    };    return {    /* 初始化函数 */        init: function(canvasId, backgroundColor, friction, gravityValue) {            if (!CanvasSupport)                return;            this.backgroundColor = backgroundColor || "#000";                //背景色,默认为黑色            this.friction = friction || 0.1;                                 //墙壁摩擦系数,默认为0.1            this.gravityValue = gravityValue || 0.2;                         //重力系数,默认为0.2            this.canvasObj = util.$(canvasId);                               //canvas对象            this.canvasWidth = util.getComputedStyle(this.canvasObj).width;  //canvas高度            this.canvasHeight = util.getComputedStyle(this.canvasObj).height;//canvas宽度            this.contextObj = this.canvasObj.getContext(2d);               //2d的context对象            this.ballArray = [];                                             //保存所有小球的数组            drawBackground(this.contextObj, this.backgroundColor, this.canvasWidth, this.canvasHeight);            setInterval(drawBallsToCanvas(this.ballArray, this.contextObj, this.backgroundColor, this.canvasWidth, this.canvasHeight, this.gravityValue, this.friction = 0.1), 33);        },        /* 添加小球 */        createBall: function(radius, color, speed, direction, currentX, currentY, elasticity) {// 小球半径 颜色 速度 方向            var ball = new newBall(radius, color, speed, direction, currentX, currentY, elasticity);            this.ballArray.push(ball);        }    }})();

点击复制链接 与好友分享!回本站首页

文章来源:http://www.bozhiyue.com/html_css/2016/0812/368025.html
返回上一页    返回分类 上一篇:   下一篇:
相关