本文详细介绍如何使用JavaScript实现10个小球在页面内的随机运动及碰撞反弹效果。通过定义Screen类管理小球状态,利用vx、vy控制移动方向,结合半径之和判断碰撞并改变速度,同时处理窗口边界反弹。代码结构清晰,包含HTML布局与JS逻辑分离,适合初学者参考学习。
JavaScript小球运动与碰撞实现概述
本文通过具体实例,讲解如何利用JavaScript实现10个小球的随机运动及相互碰撞效果。内容旨在为读者提供可直接参考的代码方案。
在掌握基础JavaScript知识后,许多开发者会尝试制作更具挑战性的动画案例。其中,实现10个小球的随机碰撞效果往往被视为一个难点。本文将此案例分享出来,希望能为正在学习编程、尤其是遇到类似困惑的初学者提供一定的启发和帮助。
核心需求如下:页面内需生成10个小球,它们进行随机移动;当小球接触到浏览器窗口边界或其他小球时,必须执行反弹动作。
碰撞检测与反弹逻辑思路
实现该效果的核心逻辑如下:
1、10个小球对应10个div元素; 2、窗口边界反弹机制:定义vx与vy作为小球移动变量,并设定弹力系数bounce(取负值),当小球触及窗口边界时,将vx与vy分别乘以bounce,从而改变小球的运动方向; 3、小球间碰撞反弹:若两小球圆心距dist小于半径之和的最小值,则调整小球的运动方向以实现反弹效果
以下是完整的实现代码:
HTML与JavaScript代码实现
HTML与JavaScript代码分文件编写。 test.html文件结构如下:
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">
body {
margin:0;
padding:0;
text-align: center;
}
#screen { width: 800px; height: 640px; position: relative; background: #ccccff;margin: 0 auto;vertical-align: bottom}
#inner { position: absolute; left:0px; top:0px; width:100%; height:100%; }
#screen p {color:white;font:bold 14px;}
.one { background-image:url('bubble.png'); background-position: -66px -58px; }
.two { background-image:url('bubble.png'); background-position: -66px -126px;}
.three { background-image:url('bubble.png'); background-position: -66px -194px; }
.four { background-image:url('bubble.png'); background-position: -66px -263px; }
.five { background-image:url('bubble.png'); background-position: -66px -331px; }
.six { background-image:url('bubble.png'); background-position: -66px -399px; }
.seven { background-image:url('bubble.png'); background-position: -66px -194px; }
.eight { background-image:url('bubble.png'); background-position: -66px -263px; }
.nine { background-image:url('bubble.png'); background-position: -66px -331px; }
.ten{ background-image:url('bubble.png'); background-position: -66px -399px; }
</style>
</head>
<body>
<div id="screen" >
<p>hi test it!</p>
<div id="inner"></div>
</div>
<input type="button" id="start" value="start" >
<input type="button" id="stop" value="stop">
<br><br><br>
<script type="text/javascript" src="test.js"></script>
</body>
</html>
test.js文件逻辑如下:
var getFlag=function (id) {
return document.getElementByIdx_x(id); //获取元素引用
}
var extend=function(des, src) {
for (p in src) {
des[p]=src[p];
}
return des;
}
var clss=['one','two','three','four','five','six','seven','eight','nine','ten'];
var Ball=function (diameter,classn) {
var ball=document.createElement_x("div");
ball.className=classn;
with(ball.style) {
width=height=diameter+'px';position='absolute';
}
return ball;
}
var Screen=function (cid,config) {
//先创建类的属性
var self=this;
if (!(self instanceof Screen)) {
return new Screen(cid,config)
}
config=extend(Screen.Config, config) //configj是extend类的实例
self.container=getFlag(cid); //窗口对象
self.ballsnum=config.ballsnum;
self.diameter=56; //球的直径
self.radius=self.diameter/2;
self.spring=config.spring; //球相碰后的反弹力
self.bounce=config.bounce; //球碰到窗口边界后的反弹力
self.gravity=config.gravity; //球的重力
self.balls=[]; //把创建的球置于该数组变量
self.timer=null; //调用函数产生的时间id
self.L_bound=0; //container的边界
self.R_bound=self.container.clientWidth;
self.T_bound=0;
self.B_bound=self.container.clientHeight;
};
Screen.Config={ //为属性赋初值
ballsnum:10,
spring:0.8,
bounce:-0.9,
gravity:0.05
};
Screen.prototype={
initialize:function () {
var self=this;
self.createBalls();
self.timer=setInterval(function (){self.hitBalls()}, 30)
},
createBalls:function () {
var self=this, num=self.ballsnum;
var frag=document.createDocumentFragment(); //创建文档碎片,避免多次刷新
for (i=0;i<num;i++) {
var ball=new Ball(self.diameter,clss[ Math.floor(Math.random()* num )]);
ball.diameter=self.diameter;
ball.radius=self.radius;
ball.style.left=(Math.random()*self.R_bound)+'px'; //球的初始位置,
ball.style.top=(Math.random()*self.B_bound)+'px';
ball.vx=Math.random() * 6 -3;
ball.vy=Math.random() * 6 -3;
frag.appendChild(ball);
self.balls[i]=ball;
}
self.container.appendChild(frag);
},
hitBalls:function () {
var self=this, num=self.ballsnum,balls=self.balls;
for (i=0;i<num-1;i++) {
var ball1=self.balls[i];
ball1.x=ball1.offsetLeft+ball1.radius; //小球圆心坐标
ball1.y=ball1.offsetTop+ball1.radius;
for (j=i+1;j<num;j++) {
var ball2=self.balls[j];
ball2.x=ball2.offsetLeft+ball2.radius;
ball2.y=ball2.offsetTop+ball2.radius;
dx=ball2.x-ball1.x; //两小球圆心距对应的两条直角边
dy=ball2.y-ball1.y;
var dist=Math.sqrt(dx*dx + dy*dy); //两直角边求圆心距
var misDist=ball1.radius+ball2.radius; //圆心距最小值
if(dist < misDist) {
//假设碰撞后球会按原方向继续做一定的运动,将其定义为运动A
var angle=Math.atan2(dy,dx);
//当刚好相碰,即dist=misDist时,tx=ballb.x, ty=ballb.y
tx=balla.x+Math.cos(angle) * misDist;
ty=balla.y+Math.sin(angle) * misDist;
//产生运动A后,tx > ballb.x, ty > ballb.y,所以用ax、ay记录的是运动A的值
ax=(tx-ballb.x) * self.spring;
ay=(ty-ballb.y) * self.spring;
//一个球减去ax、ay,另一个加上它,则实现反弹
balla.vx-=ax;
balla.vy-=ay;
ballb.vx+=ax;
ballb.vy+=ay;
}
}
}
for (i=0;i<num;i++) {
self.moveBalls(balls[i]);
}
},
moveBalls:function (ball) {
var self=this;
ball.vy+=self.gravity;
ball.style.left=(ball.offsetLeft+ball.vx)+'px';
ball.style.top=(ball.offsetTop+ball.vy)+'px';
//判断球与窗口边界相碰,把变量名简化一下
var L=self.L_bound, R=self.R_bound, T=self.T_bound, B=self.B_bound, BC=self.bounce;
if (ball.offsetLeft < L) {
ball.style.left=L;
ball.vx*=BC;
}
else if (ball.offsetLeft + ball.diameter > R) {
ball.style.left=(R-ball.diameter)+'px';
ball.vx*=BC;
}
else if (ball.offsetTop < T) {
ball.style.top=T;
ball.vy*=BC;
}
if (ball.offsetTop + ball.diameter > B) {
ball.style.top=(B-ball.diameter)+'px';
ball.vy*=BC;
}
}
}
window.onload=function() {
var sc=null;
getFlag('start').onclick=function () {
document.getElementByIdx_x("inner").innerHTML='';
sc=new Screen('inner',{ballsnum:10, spring:0.8, bounce:-0.9, gravity:0.05});
sc.initialize();
}
getFlag('stop').onclick=function() {
clearInterval(sc.timer);
}
}
代码结构与功能解析
测试结果显示效果良好。尽管代码量较大,但整体思路清晰: 首先,创建Screen类,并在其构造函数中初始化小球数量(ballsnum)、弹性系数(spring)、反弹系数(bounce)、重力加速度(gravity)等关键属性; 其次,通过原型链(prototype)定义相关方法,包括创建小球(createBalls)、检测碰撞(hitBalls)以及更新小球位置(moveBalls)等,为每个函数赋予具体功能; 最后,通过按钮点击事件触发上述函数,完成交互控制。
希望本文所述内容能对各位在JavaScript程序设计方面的学习有所帮助。