在以前的文章 Canvas基础-粒子动画Part2 和 Canvas基础-粒子动画Part3 中分别讲了用图片和文字作粒子动画,今天咱们来把代码简单整理一下,封装成一个类,能同时支持用图片和文字作粒子动画,并且有更好的灵活性。javascript
HTML结构和上一篇的同样,这里从外部引入一个js文件,咱们的类就写这里面。css
<body>
<div class="input-wrap">
<input id="txt" type="text" name="" value="" placeholder="输入发射文字...">
<button id="btn" class="btn">发射</button>
</div>
<canvas id="canvas" width="300" height="300" ></canvas>
<script type="text/javascript" src="./particle-maker.js"></script>
</body>复制代码
以后在 particle-maker.js
文件中,写咱们的类,取名叫 ParticleMaker
,而后把咱们须要的一些参数啊什么的给定义进去。html
"use strict";
var gRafId = null; //requestAnimationFrame id, new ParticleMaker() 的时候要能把前一次的动画取消
function ParticleMaker(conf) {
var me = this,
canvas = null, // canvas element
ctx = null, // canvas contex
dotList = [], // dot object list
// rafId = gRafId, // rafid, 不能放在此处,由于 new 对象的时候会覆盖,没法取消前一次的动画
finishCount = 0; // finish dot count
var fontSize = conf["fontSize"] || 500,
fontFamily = conf["fontFamily"] || "Helvetica Neue, Helvetica, Arial, sans-serif",
mass = conf["mass"] || 6, // 取样密度
dotRadius = conf["dotRadius"] || 2, // 点半径
startX = conf["startX"] || 400, // 开始位置X
startY = conf["startY"] || 400, // 开始位置Y
endX = conf["endX"] || 0, // 结束位置X
endY = conf["endY"] || 0, // 结束位置Y
effect = conf["effect"] || "easeInOutCubic", // 缓动函数
fillColor = conf["fillColor"] || "#000", // 填充颜色
content = conf["content"] || "Beta"; // 要画的东西,若是是图片须要 new Image() 传进来
// 缓动函数
// t 当前时间
// b 初始值
// c 总位移
// d 总时间
var effectFunc = {
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;
},
easeInCirc: function (t, b, c, d) {
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
},
easeOutQuad: function (t, b, c, d) {
return -c *(t/=d)*(t-2) + b;
}
}
if (typeof effectFunc[effect] !== "function") {
console.log("effect lost, use easeInOutCubic");
effect = "easeInOutCubic";
}
function Dot(centerX, centerY, radius) {
this.x = centerX;
this.y = centerY;
this.radius = radius;
this.frameNum = 0;
this.frameCount = Math.ceil(3000 / 16.66);
this.sx = startX;
this.sy = startY;
this.delay = this.frameCount*Math.random();
this.delayCount = 0;
}
}复制代码
ParticleMaker
类里面,下次 new 的时候会覆盖,这样就无法取消掉以前的动画了;easeInOutCubic
更多的缓动函数也按这个形式添加就能够了;这步比较简单,看过以前文章的比较好理解。添加完类,咱们再把以前用到的几个函数给弄过来。java
this._setFontSize = function(s) {
ctx.font = s + 'px ' + fontFamily;
}
this._isNumber = function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
this._cleanCanvas = function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
this._handleCanvas = function() {
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// console.log(imgData);
for(var x=0; x<imgData.width; x+=mass) {
for(var y=0; y<imgData.height; y+=mass) {
var i = (y*imgData.width + x) * 4;
if(imgData.data[i+3] > 128 && imgData.data[i] < 100){
var dot = new Dot(x, y, dotRadius);
dotList.push(dot);
}
}
}
}复制代码
除了用来清除画布的 _cleanCanvas
是新定义的,其它三个函数是咱们以前用过的,主要根据类的参数对以前的变量作一些改动。git
好比 _handleCanvas
中的循环 for(var x=0; x
程序员
另外不要吐槽个人命名,下划线开头表示私有函数,Python你懂的。github
以后咱们须要一个 render
方法,用来把那些通过 _handleCanvas
处理以后的点,给渲染出来。canvas
this.render = function() {
me._cleanCanvas();
ctx.fillStyle = fillColor;
var len = dotList.length,
curDot = null,
frameNum = 0,
frameCount = 0,
curX, curY;
finishCount = 0;
for(var i=0; i < len; i+=1) {
// 当前粒子
curDot = dotList[i];
// 获取当前的time和持续时间和延时
frameNum = curDot.frameNum;
frameCount = curDot.frameCount;
if(curDot.delayCount < curDot.delay){
curDot.delayCount += 1;
continue;
}
ctx.save();
ctx.beginPath();
if(frameNum < frameCount) {
curX = effectFunc[effect](frameNum, curDot.sx, curDot.x-curDot.sx, curDot.frameCount);
curY = effectFunc[effect](frameNum, curDot.sy, curDot.y-curDot.sy, curDot.frameCount);
ctx.arc(curX, curY, curDot.radius, 0, 2*Math.PI);
curDot.frameNum += 1;
} else {
ctx.arc(curDot.x, curDot.y, curDot.radius, 0, 2*Math.PI);
finishCount += 1;
}
ctx.fill();
ctx.restore();
if (finishCount >= len) {
// console.log(gRafId);
cancelAnimationFrame(gRafId);
return conf["onFinish"] && conf["onFinish"]();
}
}
// gRafId = requestAnimationFrame(arguments.callee);
gRafId = requestAnimationFrame(me.render);
}复制代码
这个函数大致和 Canvas基础-粒子动画Part2 中的同样,为了阅读连贯性,我把其中的解释给拷贝过来了:浏览器
frameNum < frameCount
,经过前面的缓动函数计算出当前应该到达的x,y值,而后画到Canvas上并将这个点的帧数加一。else
条件,就不要画计算出来的值了,画实际应该在的位置。ctx.beginPath()
和ctx.fill()
,否则你的画布上啥子都没有。finishCount
,用来在每次画粒子的时候统计有多少个是已经跑到相应位置了,因此每次循环开始前都要将其置为0,当跑到位的粒子数量和总粒子数量相等的时候,就调用cancelAnimationFrame
并退出,停掉相应的绘制,不要浪费资源。ctx.fill()
以后作,否则有会出现少了一个粒子的状况。这里对其作了一些小改动:微信
effectFunc[effect]
缓动函数从配置中读取;conf["onFinish"] && conf["onFinish"]()
当初始化的配置中有设置完成的回调时,这里调用一下。requestAnimationFrame(arguments.callee)
这里特别说明一下,原本调用函数自己这个是想用 arguments.callee
来作的,callee
表示正被执行的函数对象,也就是 render
函数自己,可是咱们在文件开头声明了使用严格模式 use strict
,严格模式下不给用arguments, caller, callee
,因此换成了 gRafId = requestAnimationFrame(me.render)
。最后咱们须要让动画跑起来的 run
方法和支持画文字和画图片的 drawText
和 drawImage
方法。
this.run = function() {
if( !conf["canvasId"] ){
console.log("No canvas Id");
return;
}
// 有正在运行的动画要取消掉
if (gRafId) cancelAnimationFrame(gRafId);
dotList = [];
finishCount = 0;
canvas = document.getElementById(conf["canvasId"]);
ctx = canvas.getContext("2d");
this._cleanCanvas();
var drawFunc = this.drawText;
if( typeof content === "object" && content.src && content.src != "" ){
drawFunc = this.drawImage;
}
drawFunc(content);
// Move to this._run();
// this._handleCanvas();
// this._cleanCanvas();
// this.render();
}
this._run = function(){
// ctx.save();
this._handleCanvas();
this._cleanCanvas();
this.render();
}
this.drawText = function(l) {
// init canvas
ctx.textBaseline = "top";
me._setFontSize(fontSize);
var s = Math.min(fontSize,
(canvas.width / ctx.measureText(l).width) * 0.8 * fontSize,
(canvas.height / fontSize) * (me._isNumber(l) ? 1 : 0.5) * fontSize);
me._setFontSize(s);
ctx.fillStyle = "#000";
ctx.fillText(l, endX, endY); // 最后位置
me._run();
}
this.drawImage = function(img) {
if(img.complete){
ctx.drawImage(img, endX, endY);
me._run();
} else {
img.onload = function(){
ctx.drawImage(img, endX, endY);
me._run();
}
}
}复制代码
由于画文字是很快的,能够是顺序同步的,而画图片可能有一个等待图片 onload
的过程,这里是可能有异步调用的状况。下面来解释一下:
首先是 run
方法,作的事情比较简单:
canvasId
, 没有就不搞了;_run
方法,这个是调用画文字或者图片以后要执行的步骤,由于有等待图片异步调用的状况,因此要单独出来。
drawText
方法比较简单,判断 fontSize 是否合适,写文字上去,而后当即调用 _run
方法。
drawImage
方法首先用 compelete
属性判断一下图片是否加载完了,没加载完则设个 onload
事件,等加载完再画图片以及调用 _run
方法。
到这里整个类就基本OK了,为了不 requestAnimationFrame
方法在部分浏览器没有,能够加个polyfill。
var requestAnimationFrame = window.requestAnimationFrame ||
function(callback) {
return window.setTimeout(callback, 1000 / 60);
};
var cancelAnimationFrame = window.cancelAnimationFrame ||
function(id) {
window.clearTimeout(id);
}复制代码
简单写下调用方法:
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext('2d'),
winWidth = document.documentElement.clientWidth,
winHeight = document.documentElement.clientHeight;
canvas.width = winWidth;
canvas.height = winHeight;
document.querySelector("#btn").addEventListener("click", function(){
init();
})
function init() {
var s = 0;
input = document.querySelector("#txt");
// var l = input.value ? input.value : "Beta";
var l = input.value;
if( !input.value ) {
l = new Image();
l.src = "images.jpeg";
}
input.value = "";
// normal useage
var particleMaker = new ParticleMaker({
canvasId: "canvas",
startX: 200,
startY: 400,
endX: 10,
endY: 40,
// mess: 10,
// dotRadius: 3,
content: l,
fillColor: "#ff4444",
effect: "easeOutQuad",
onFinish: function(){
console.log("onFinish");
console.log(l);
}
});
particleMaker.run();
}复制代码
代码比较简单,一开始给 Canvas 设置各类属性,而后当点击按钮的时候,调用 init
方法, init 方法中判断下输入框有没有输入过东西,没输入东西就拿个图片作,输入过东西就把输入的东西做为 content
参数的值传进去。
这里的图片用的是这样的:
效果:
控制台也能够看到 onFinish
回调的输出:
onFinish
<img src="images.jpeg">
onFinish
掘金复制代码
最后咱们再来折腾一下,让咱们的类不只能够普通调用,还能够支持 seajs
和 requirejs
。
在类的外面,加入如下代码就搞定了:
// AMD & CMD Support
window.ParticleMaker = ParticleMaker;
if (typeof define === "function") {
define(function(require, exports, module) {
module.exports = ParticleMaker;
})
}复制代码
调用:
先从CDN上搞个 seajs 来:
<!--<script type="text/javascript" src="./particle-maker.js"></script>-->
<script src="//cdn.bootcss.com/seajs/3.0.2/sea.js"></script>复制代码
而后修改下 init
函数里面的调用:
// seajs useage
seajs.use("./particle-maker", function(ParticleMaker) {
var particleMaker = new ParticleMaker({
canvasId: "canvas",
startX: 200,
startY: 400,
endX: 10,
endY: 40,
// mess: 10,
// dotRadius: 3,
content: l,
fillColor: "#ff4444",
effect: "easeOutQuad",
onFinish: function() {
console.log("onFinish");
console.log(l);
}
});
particleMaker.run();
});复制代码
到这里就基本搞完了,代码比较多,推荐跑一下源码,对照着看,有不清楚的也能够翻翻以前的文章,或者留言交流哈。
ParticleMaker的GitHub地址: github.com/bob-chen/Pa…
Demo的源码地址: github.com/bob-chen/ca…
最近总想记录一些所思所想,写写科技与人文,写写生活状态,写写读书感悟,发在微信公众平台上,主要是扯淡和感悟,欢迎关注,交流。
微信公众号:程序员的诗和远方
公众号ID : MonkeyCoder-Life