本文章適合于有一定canvas畫圖經(jīng)驗(yàn)的人閱讀,能夠熟悉基本的繪圖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", // (必選)指定一個(gè)父容器
"bgcolor": "#222222", // (可選)畫布的背景顏色
"color": "#ffffff", // (可選)粒子的顏色
"concentration": 0.2, // (可選)濃度
"radius": 5, // (可選)例子半徑
"opacity": 0.9, // (可選)粒子透明度
"duration": 16, // (可選)運(yùn)動(dòng)的時(shí)間(秒)大概值不一定精確
"rangeRadius": 512, // (可選)粒子運(yùn)動(dòng)的范圍
}
var test = new granule(setting); // 設(shè)置動(dòng)畫的屬性
test.startAnimation(); // 啟動(dòng),開始運(yùn)行
</script>
</html>
上面是html代碼,有一個(gè)canvasView的大容器,等下我們會(huì)在這里繪制canvas。
setting里面是一些比較總要的參數(shù),包括粒子濃度,運(yùn)動(dòng)范圍,背景顏色等。
首先第一步,寫好入口文件
var granule = function(setting) {
this.TWEEN = ["easeInOutQuad", "easeInOutCubic", "easeInOutBack"];
this.sett = setting
this._init();
this.randomCreateAllBall();
}
/**
* 用于初始化
*
* 創(chuàng)建畫布: this.canvas;
* 畫圖環(huán)境:this.cc
* 計(jì)算寬高:this.cw, this.ch
* 圓的半徑: this.radius , 默認(rèn)5px
* 例子的濃度: this.concentration
* 粒子的運(yùn)動(dòng)時(shí)間: this.duration
* 粒子的 活動(dòng)范圍半徑:this.rangeRadius
* 計(jì)算粒子數(shù)量: 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 是粒子運(yùn)動(dòng)的幅度大小
2.this.cd 是運(yùn)動(dòng)的粒子數(shù)量
如何保證粒子不間斷的運(yùn)動(dòng)? 需要給每個(gè)粒子設(shè)定一個(gè)運(yùn)動(dòng)的范圍,運(yùn)動(dòng)的形式可以用緩動(dòng)函數(shù),下面會(huì)給出來。在還沒到達(dá)這個(gè)運(yùn)動(dòng)終點(diǎn)時(shí),需要不停的重繪,當(dāng)?shù)竭_(dá)時(shí)有需要給這個(gè)粒子重新設(shè)置一個(gè)運(yùn)動(dòng)的軌跡,大概原理就是這樣,先看一下如何畫出所有粒子。
granule.prototype.randomCreateAllBall = function() {
var all = [];
for(var i = 0; i < this.cg; i++) {
var ball = {};
ball.stauts = "0"; // 增加一個(gè)特殊狀態(tài),這個(gè)狀態(tài)表示該粒子目前時(shí)候處于 [自定義動(dòng)畫中](自定義動(dòng)畫:輸入指令而顯示的動(dòng)畫)。0:表示目前沒有自定義動(dòng)畫
ball.color = this.color;
ball.r = this.radius;
ball.x = this.rdmmm(0, this.cw); // 實(shí)際坐標(biāo)x, 對(duì)應(yīng)tween中的b:起點(diǎn)
ball.y = this.rdmmm(0, this.ch); // 實(shí)際坐標(biāo)y,
ball.txb = ball.x; // 對(duì)應(yīng)tween中的b:起點(diǎn)
ball.tyb = ball.y; // 起點(diǎn), 對(duì)應(yīng)y坐標(biāo)
ball.txc = this.rdmmm(-this.rangeRadius, this.rangeRadius); // 對(duì)應(yīng)tween中的c:位移(可以隨機(jī)一個(gè))
ball.tyc = this.rdmmm(-this.rangeRadius, this.rangeRadius);
ball.td = (this.duration + this.rdmmm(-this.duration / 2, this.duration / 2)) * 60; // 對(duì)應(yīng)tween中的d:終止時(shí)間,建議設(shè)置為60的倍數(shù)
ball.tt = this.rdmmm(0, ball.td); // 對(duì)應(yīng)tween中的t:時(shí)間 注意這里的時(shí)間不是從0開始 意味著一開始繪制的時(shí)候粒子的坐標(biāo)并不是剛生成時(shí)的坐標(biāo)
if(this.tweenType) {
ball.tp = this.tweenType
} else {
ball.tp = this.TWEEN[this.rdmmm(0, this.TWEEN.length)];
//每一粒子的運(yùn)動(dòng)函數(shù)都是隨機(jī)的
}
all.push(ball);
}
this.balls = all;
}
/**
* 生成指定范圍的隨機(jī)數(shù)
* @param {int} min: 隨機(jī)數(shù)的下限
* @param {int} max: 隨機(jī)數(shù)的上限
*/
granule.prototype.rdmmm = function(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
為了方便理解上面代碼,看一下給出的運(yùn)動(dòng)函數(shù)
/**
this.TWEEN = ["easeInOutQuad", "easeInOutCubic", "easeInOutBack"];
* 二次緩動(dòng), 具體請(qǐng)查看Tween源碼
* @param {int} t:當(dāng)前時(shí)間
* @param {int} b:初始值
* @param {int} c:變化量, 位移
* @param {int} d:持續(xù)時(shí)間
*/
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;
}
// 三次緩動(dòng)
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)里面的參數(shù)
t:動(dòng)畫開始時(shí)間 b:動(dòng)畫開始位置 c:動(dòng)畫結(jié)束位置 d:動(dòng)畫持續(xù)時(shí)間
因此我們可以通過給t設(shè)定一個(gè)值,得到他該時(shí)間點(diǎn)上的具體位置。
好了,所有的粒子都已經(jīng)畫出來了,運(yùn)動(dòng)軌跡也已經(jīng)設(shè)置好,接下來就是每一幀的重繪了。
granule.prototype.updataBallPosition = function(b) {
// 根據(jù) tween 計(jì)算在當(dāng)前幀的位置
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);
// 四個(gè)方向的碰撞檢測(cè)
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;
// 當(dāng)運(yùn)動(dòng)時(shí)間結(jié)束之后 普通的粒子重新隨機(jī)一個(gè)位移
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;
}
當(dāng)b.tt >= b.td 也就是該粒子運(yùn)動(dòng)的時(shí)間到了之后,需要重新給他設(shè)置一下。
最后就是要啟動(dòng)該動(dòng)畫
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();
}
}
如果對(duì)requestAnimationFrame不熟悉的同學(xué)自行g(shù)oogle。在每一幀調(diào)用self.updataBallPosition(ball)計(jì)算出該求得位置,self.drawBalls()畫出該球。
好了,到這里也還差不多結(jié)束了,需要思考的是小球的運(yùn)動(dòng)軌跡改變和每一幀的重繪,當(dāng)碰撞后怎么辦。。。