本文章适合于有一定canvas画图经验的人阅读,能够熟悉基本的绘图API,废话不多说,下面就开始了。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.canvasview {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div class="canvasview"></div>
</body>
<script type="text/javascript" src="granule.js" ></script>
<script>
var setting = {
"el": ".canvasview", // (必选)指定一个父容器
"bgcolor": "#222222", // (可选)画布的背景颜色
"color": "#ffffff", // (可选)粒子的颜色
"concentration": 0.2, // (可选)浓度
"radius": 5, // (可选)例子半径
"opacity": 0.9, // (可选)粒子透明度
"duration": 16, // (可选)运动的时间(秒)大概值不一定精确
"rangeRadius": 512, // (可选)粒子运动的范围
}
var test = new granule(setting); // 设置动画的属性
test.startAnimation(); // 启动,开始运行
</script>
</html>
上面是html代码,有一个canvasView的大容器,等下我们会在这里绘制canvas。
setting里面是一些比较总要的参数,包括粒子浓度,运动范围,背景颜色等。
首先第一步,写好入口文件
var granule = function(setting) {
this.TWEEN = ["easeInOutQuad", "easeInOutCubic", "easeInOutBack"];
this.sett = setting
this._init();
this.randomCreateAllBall();
}
/**
* 用于初始化
*
* 创建画布: this.canvas;
* 画图环境:this.cc
* 计算宽高:this.cw, this.ch
* 圆的半径: this.radius , 默认5px
* 例子的浓度: this.concentration
* 粒子的运动时间: this.duration
* 粒子的 活动范围半径:this.rangeRadius
* 计算粒子数量: this.cg
*/
granule.prototype._init = function() {
var el = document.querySelector(this.sett.el);
var elbox = el.getBoundingClientRect();
this.canvas = document.createElement("canvas");
this.cc = this.canvas.getContext("2d");
this.cw = parseInt(this.sett.width) || elbox.width;
this.ch = parseInt(this.sett.height) || elbox.height;
this.radius = this.sett.radius || 5;
this.opacity = this.sett.opacity || 1;
this.concentration = this.sett.concentration || 1;
this.duration = this.sett.duration || 8;
this.rangeRadius = this.sett.rangeRadius || (this.cw + this.ch) / 4;
this.tweenType = this.sett.tweenType || "";
this.tweenAni = this.sett.tweenAni || "easeInOutCubic";
this.cg = Math.floor(this.cw * this.ch / (this.radius * this.radius * 8 * 8) * this.concentration);
this.color = this.sett.color || "#ffffff";
this.bgcolor = this.sett.bgcolor || "#222222";
this.canvas.width = this.cw;
this.canvas.height = this.ch;
el.appendChild(this.canvas);
}
1.this.rangeRadius 是粒子运动的幅度大小
2.this.cd 是运动的粒子数量
如何保证粒子不间断的运动? 需要给每个粒子设定一个运动的范围,运动的形式可以用缓动函数,下面会给出来。在还没到达这个运动终点时,需要不停的重绘,当到达时有需要给这个粒子重新设置一个运动的轨迹,大概原理就是这样,先看一下如何画出所有粒子。
granule.prototype.randomCreateAllBall = function() {
var all = [];
for(var i = 0; i < this.cg; i++) {
var ball = {};
ball.stauts = "0"; // 增加一个特殊状态,这个状态表示该粒子目前时候处于 [自定义动画中](自定义动画:输入指令而显示的动画)。0:表示目前没有自定义动画
ball.color = this.color;
ball.r = this.radius;
ball.x = this.rdmmm(0, this.cw); // 实际坐标x, 对应tween中的b:起点
ball.y = this.rdmmm(0, this.ch); // 实际坐标y,
ball.txb = ball.x; // 对应tween中的b:起点
ball.tyb = ball.y; // 起点, 对应y坐标
ball.txc = this.rdmmm(-this.rangeRadius, this.rangeRadius); // 对应tween中的c:位移(可以随机一个)
ball.tyc = this.rdmmm(-this.rangeRadius, this.rangeRadius);
ball.td = (this.duration + this.rdmmm(-this.duration / 2, this.duration / 2)) * 60; // 对应tween中的d:终止时间,建议设置为60的倍数
ball.tt = this.rdmmm(0, ball.td); // 对应tween中的t:时间 注意这里的时间不是从0开始 意味着一开始绘制的时候粒子的坐标并不是刚生成时的坐标
if(this.tweenType) {
ball.tp = this.tweenType
} else {
ball.tp = this.TWEEN[this.rdmmm(0, this.TWEEN.length)];
//每一粒子的运动函数都是随机的
}
all.push(ball);
}
this.balls = all;
}
/**
* 生成指定范围的随机数
* @param {int} min: 随机数的下限
* @param {int} max: 随机数的上限
*/
granule.prototype.rdmmm = function(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
为了方便理解上面代码,看一下给出的运动函数
/**
this.TWEEN = ["easeInOutQuad", "easeInOutCubic", "easeInOutBack"];
* 二次缓动, 具体请查看Tween源码
* @param {int} t:当前时间
* @param {int} b:初始值
* @param {int} c:变化量, 位移
* @param {int} d:持续时间
*/
granule.prototype.easeInOutQuad = function(t, b, c, d) {
if((t /= d / 2) < 1) return c / 2 * t * t + b;
return -c / 2 * ((--t) * (t - 2) - 1) + b;
}
// 三次缓动
granule.prototype.easeInOutCubic = function(t, b, c, d) {
if((t /= d / 2) < 1) return c / 2 * t * t * t + b;
return c / 2 * ((t -= 2) * t * t + 2) + b;
}
// bakc
granule.prototype.easeInOutBack = function(t, b, c, d, s) {
if(s == undefined) s = 1.70158;
if((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
}
// 弹簧效果
granule.prototype.easeOutBounce = function(t, b, c, d) {
if((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) + b;
} else if(t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
} else if(t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
} else {
return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
}
}
我来介绍一下function(t,b,c,d)里面的参数
t:动画开始时间 b:动画开始位置 c:动画结束位置 d:动画持续时间
因此我们可以通过给t设定一个值,得到他该时间点上的具体位置。
好了,所有的粒子都已经画出来了,运动轨迹也已经设置好,接下来就是每一帧的重绘了。
granule.prototype.updataBallPosition = function(b) {
// 根据 tween 计算在当前帧的位置
var newx = this[b.tp](b.tt, b.txb, b.txc, b.td);
var newy = this[b.tp](b.tt, b.tyb, b.tyc, b.td);
// 四个方向的碰撞检测
if(newx < 0) {
newx = -newx;
}
if(newy < 0) {
newy = -newy;
}
if(newx > this.cw) {
newx = 2 * this.cw - newx;
}
if(newy > this.ch) {
newy = 2 * this.ch - newy;
}
b.x = newx;
b.y = newy;
// 当运动时间结束之后 普通的粒子重新随机一个位移
if(b.stauts == 0) {
if(++b.tt >= b.td) {
b.txb = b.x;
b.tyb = b.y;
b.txc = this.rdmmm(-this.rangeRadius, this.rangeRadius);
b.tyc = this.rdmmm(-this.rangeRadius, this.rangeRadius);
b.tt = 0;
}
}
return b;
}
当b.tt >= b.td 也就是该粒子运动的时间到了之后,需要重新给他设置一下。
最后就是要启动该动画
granule.prototype.startAnimation = function() {
var self = this;
var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
start();
function start() {
for(var i = 0; i < self.balls.length; i++) {
var ball = self.balls[i];
var newb = self.updataBallPosition(ball);
self.balls[i] = newb;
}
self.drawBalls();
raf(start);
}
}
/**
* 绘制所有的小球
*/
granule.prototype.drawBalls = function() {
this.drawbg();
this.cc.globalAlpha = this.opacity;
//基本画圆Api
for(var i = 0; i < this.balls.length; i++) {
var ball = this.balls[i];
this.cc.beginPath();
this.cc.fillStyle = ball.color || this.color;
this.cc.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
this.cc.closePath();
this.cc.fill();
}
}
如果对requestAnimationFrame不熟悉的同学自行google。在每一帧调用self.updataBallPosition(ball)计算出该求得位置,self.drawBalls()画出该球。
好了,到这里也还差不多结束了,需要思考的是小球的运动轨迹改变和每一帧的重绘,当碰撞后怎么办。。。