上一小节我们实现了一个小球运动,太孤单了,现在我们来给它添加些小伙伴~
面向对象编程
我们先将编写小球的类,基本就是将上一节小球属性复制过来,只不过我们需在实例化时给它传递参数,用于控制小球属性,毕竟每个小球长一样也不好玩啦。
提示: Object.assign 是es6的接口,文章结尾给了个polyfill版本
function Ball(options) {
var opts = Object.assign({
x: 50, // x坐标
y: 50, // y坐标
r: 30, // 小球半径
color: 'blue', // 小球填充颜色
vx: 10, // 小球x轴速度 px/帧
vy: 10 // 小球y轴速度 px/帧
}, options);
this.x = opts.x;
this.y = opts.y;
this.r = opts.r;
this.color = opts.color;
this.vx = opts.vx;
this.vy = opts.vy;
}
再将上一节的update与draw函数作为此类的方法
/**
* 更新小球位置,基于帧速率
* @param dt 此帧离上帧间隔时间
*/
Ball.prototype.update = function (dt) {
var that = this;
that.x += that.vx;
that.y += that.vy;
if (that.x - that.r <= 0 ) { // 小球碰到了左边
that.x = that.r;
that.vx *= -1;
} else if (that.x + that.r >= cvs.width) { // 小球碰到了右边
that.x = cvs.width - that.r;
that.vx *= -1;
}
if (that.y - that.r <= 0 ) { // 小球碰到了上边
that.y = that.r;
that.vy *= -1;
} else if (that.y + that.r >= cvs.height) { // 小球碰到了下边
that.y = cvs.height - that.r;
that.vy *= -1;
}
};
/**
* 将小球画到画布上显示出来
* @param ctx 画面上下文对象
*/
Ball.prototype.draw = function (ctx) {
var that = this;
// 不能在此处作清除背景的操作啦,毕竟我是要画多个小球,需放到animate函数中
// ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = that.color;
ctx.beginPath();
ctx.arc(that.x, that.y, that.r, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
};
修改上一小节的逻辑,看看更改为类的方式,是否运行出错
var cvs = document.getElementById('canvas'), // 获取画布元素
ctx = cvs.getContext('2d'); // 获取画面上下文对象,用于画图
cvs.width = 800; // 设置画布宽度
cvs.height = 600; // 设置画布高度
// 小球相关属性
var ball = new Ball(); // 使用默认属性实例化一个小球
var lasttime = undefined;
function animate(time) {
if (lasttime === undefined) {
lasttime = time;
} else {
var dt = time - lasttime;
lasttime = time;
// 清屏操作放到此处,每一帧只需清屏一次
ctx.clearRect(0, 0, cvs.width, cvs.height);
ball.update(dt); // 更新小球位置
ball.draw(ctx); // 更新后,将小球画出来
}
requestAnimationFrame(animate);
}
requestAnimationFrame(animate); // 调用requestAnimationFrame,使浏览器在最佳时机执行回调函数
添加多个小球
上一步运行代码没出错,小球跑的很欢乐~
此步就给它加几伙伴了。
var cvs = document.getElementById('canvas'), // 获取画布元素
ctx = cvs.getContext('2d'); // 获取画面上下文对象,用于画图
cvs.width = 800; // 设置画布宽度
cvs.height = 600; // 设置画布高度
// 定义存放小球数组
var balls = [];
/**
* 添加小球的函数
* @param n 需要添加多少个小球,若不传或传非数字则表示添加1个小球
*/
function addBalls(n) {
n = n || parseInt(n);
if (isNaN(n)) {
n = 1;
}
for (var i = 0; i < n; ++i) {
balls.push(new Ball({
x: Math.random() * (cvs.width - 200) + 100, // 随机位置
y: Math.random() * (cvs.height - 200) + 100,
r: Math.random() * 10 + 20, // 随机半径
// 随机填充色
color: 'rgb('+(Math.random() * 200 + 55 )+','+(Math.random() * 200 + 55 )+','+(Math.random() * 200 + 55 )+')'
// 速度使用默认的
}));
}
}
// 小球相关属性
// var ball = new Ball(); // 使用默认属性实例化一个小球
// 实例化多个小球,此处10个
addBalls(10);
var lasttime = undefined;
function animate(time) {
if (lasttime === undefined) {
lasttime = time;
} else {
var dt = time - lasttime;
lasttime = time;
// 清屏操作放到此处,每一帧只需清屏一次
ctx.clearRect(0, 0, cvs.width, cvs.height);
// ball.update(dt); // 更新小球位置
// ball.draw(ctx); // 更新后,将小球画出来
// 遍历所有小球,更新所有小球位置,并画出来
balls.forEach(function (ball) {
ball.update(dt);
ball.draw(ctx);
})
}
requestAnimationFrame(animate);
}
requestAnimationFrame(animate); // 调用requestAnimationFrame,使浏览器在最佳时机执行回调函数
运行代码可以看到,10个小球都有,且颜色及初始位置也不致
按钮控制多次添加小球
细心的小伙伴,肯定发现上图中有个“添加100个小球”的按钮,我们现在来实现此操作。
css更改为如下代码:
body{
text-align: center;
}
#container{
margin-top: 30px;
display: inline-block;
border-radius: 10px;
box-shadow: 0 0 10px 0 #999;
padding: 20px;
}
canvas{
background-color: #fff;
border: 1px solid #000;
}
button{
margin-bottom: 20px;
}
html更改为如下代码:
<div id="container">
<button type="button" id="addBallBtn">添加100个小球</button>
<div>
<canvas id="canvas"></canvas>
</div>
</div>
javascript代码只需在结尾添加如下代码:
var cvs = document.getElementById('canvas'), // 获取画布元素
// ....
// 省略其它未更改的代码
// ....
requestAnimationFrame(animate); // 调用requestAnimationFrame,...
var addBallBtn = document.getElementById('addBallBtn');
addBallBtn.addEventListener('click', function () {
addBalls(100);
}, false);
运行代码,尝试点击按钮添加多个小球,看看运行效果。
几千上万小球的躁动
连续不断的点击添加小球的按钮,当小球个数达到几千上万时,你会明显感觉小球跑的没刚开始快了,理论上我们设置的小球的速度没做更改,它的速度就应该不会变啊。这是为什么呢?且看下回分解 0.0
Object.assign 的polyfill
如下是我写的个Object.assign的polyfill版本,需要的可以看看
Object.assign = Object.assign || function () {
var args = Array.prototype.slice.call(arguments, 0);
if (args.length === 0) {
throw new Error("必需传入至少一个参数");
}
if (Object.prototype.toString.call(args[0]) !== '[object Object]') {
throw new Error("第一个参数必须为Object类型");
}
if (args.length === 1) {
return args[0];
} else {
var result = args[0];
for (var i = 1, len = args.length; i < len; ++i) {
if (Object.prototype.toString.call(args[i]) === '[object Object]') {
for (var key in args[i]) {
if (args[i].hasOwnProperty(key)) {
result[key] = args[i][key]
}
}
}
}
return result;
}
}