【CSON原创】 基于HTML5的小球物理测试系统

效果预览:
 
半径:
颜色:
速度:
弹性(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.cnblogs.com/Cson/archive/2011/06/30/2094752.html

转载于:https://www.cnblogs.com/Cson/archive/2011/06/30/HTML5.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值