@YuriTu
2016-09-20T09:35:35.000000Z
字数 6134
阅读 808
canvas
一直想给FED的网站做个好看的首页引导图,看到,webQQ JX框架的这种效果很酷炫,决定模仿一个。
demo:粒子动画(一)demo
我们知道canvas之所以强大的原因是它像素级的操作,在画布中的每一个像素都能被我们所操作,而之所以能被操作的原因就是 ImageData对象,这个对象保存着所有的像素数据。
canvas元素的context中有一个 getImage的api可以拿到当前画布的ImageData,
他的结果:
其中的data是一个一维数据,存放着整个二维平面的数据
类似这样的结构,每4个index,组成一个rgba的数据来描述像素点,所以就构成了如下的数据结构
data = [r,g,b,a,r,b,g,a,........]
和我们在css中使用的rgba不同,其4个数字的有效范围都是0-255,a也是。
一个关键的思路就是,这个动画要逆向去构建,一堆散乱的粒子拼成特定字符是不好做的,但是把一个图片、文字随机打散到画布就不难了。
3D效果也做好后,就做动画,随机出一个坐标,让粒子先呆在那个坐标,然后通过保存的位置再聚合,然后再随机出坐标再分散,就是这个动画的原理了。
1.取得文字信息
2.创造粒子
3. 重排粒子
// 取得文字信息
this.dots = this.getImgData("人人FED");
let dots = this.dots;
// 绘制随机点
dots.forEach((item)=>{
//告诉各个粒子现在应该所在的位置
item.x = Math.random() * canvas.obj.width;
item.y = Math.random() * canvas.obj.height;
// 焦距,利用二维方式模拟三维效果
item.z = Math.random() * this.focallength * 2 - this.focallength;
//报存粒子飞散后的位置
item.tx = Math.random() * canvas.obj.width;
item.ty = Math.random() * canvas.obj.height;
item.tz = Math.random() * this.focallength * 2 - this.focallength;
//对粒子进行绘制
item.print();
})
// 动画模拟
this.animate()
首先取得文字信息
getImgData(text){
//使用canvas的api绘制了一个字符在画布上
this.drawText(text);
let imgData = c.getImageData(0,0,canvas.obj.width,canvas.obj.height)
// 清楚画布,因为要抹去文字,开始粒子化
c.clearRect(0,0,canvas.obj.width,canvas.obj.height)
//用于存储所有的粒子
let dots = [];
//粒子数量的比值 1->40k+ 5 ->1.4k+
let baseNum =5;
// 在所有的点中,取1/5 之后筛选出符合要求的点
//可以理解长i为图片某像素的x坐标,j为y坐标
for( let i = 0;i < imgData.width;i += baseNum){
for(let j = 0;j < imgData.height; j += baseNum){
// 记得我们上图的那个excel表格吗?
//取得img中的a 别忘了我们从0开始所以在加一个x坐标
let a = (j * imgData.width + i) * 4;
//只要透明度>0.5 都是文字的组成像素
if(imgData.data[a] >= 128){
//创建粒子,并告诉他,他该移动的终点的xyz坐标与小粒子的radius
let dot = new Dot(i,j,0,2);
dots.push(dot);
}
}
}
return dots;
}
二维数组查找的思路如下
Dot对象中保存着3对xyz坐标,分别是 粒子目的地位置、飞散后的位置、当前位置
constructor(centerX,centerY,centerZ,radius){
// 粒子目的地位置 即字的位置
this.dx = centerX;
this.dy = centerY;
this.dz = centerZ;
//保存粒子聚合后又飞散开的位置
this.tx = 0;
this.ty = 0;
this.tz = 0;
// 粒子现在的位置
this.x = centerX;
this.y = centerY;
this.z = centerZ;
this.radius = radius;
}
接下来就该让我们的像素动起来了
animate(){
//每次开始动画时清除前面的画面
c.clearRect(0,0,canvas.obj.width,canvas.obj.height);
//dots保存着所有的粒子
this.dots.forEach((item)=>{
// 判断飞散的粒子是否回到了原位 判断与目标位置的差距小于0.1,即为约达到了原位置;因为涉及小数对比没法直接用 ===
let flag = Math.abs(item.dx - item.x) < 0.1 && Math.abs(item.dy - item.y) < 0.1 && Math.abs(item.dz - item.z) < 0.1;
// 粒子是否回到了飞散后的位置
let flagSecond = Math.abs(item.tx - item.x) < 0.1 && Math.abs(item.ty - item.y)<0.1 && Math.abs(item.tz - item.z) < 0.1;
const speed = 0.1;
if(this.derection){
if(flag){
// 粒子回到了目的地 准备返回
item.x = item.dy;
item.y = item.dy;
item.z = item.dz;
//到达目的地反向
this.derection = false;
}else {
// 粒子没到目的地呢,继续走
// 控制行进的坐标
item.x = item.x + (item.dx -item.x) * speed;
item.y = item.y + (item.dy -item.y) * speed;
item.z = item.z + (item.dz -item.z) * speed;
}
}else{
// 飞回到了飞散后的位置
if(flagSecond){
item.x = item.tx;
item.y = item.ty;
item.z = item.tz;
this.derection = true;
} else {
// 没飞到,继续飞
item.x = item.x + (item.tx -item.x) * speed;
item.y = item.y + (item.ty -item.y) * speed;
item.z = item.z + (item.tz -item.z) * speed;
}
}
//每个粒子的重新渲染
item.print(this.focallength);
})
window.requestAnimationFrame(this.animate.bind(this));
}
至此我们的动画效果就完成了~100行多点,动手试试吧~
参考资料:
1.打造高大上的Canvas粒子动画
2.事情没有想象中那么难
3.随便谈谈用canvas来实现文字图片粒子化
源码:
let canvas = {};
canvas.obj = document.getElementById("world");
canvas.ctx = canvas.obj.getContext("2d")
let c = canvas.obj.getContext("2d")
window.requestNextAnimationFrame = () =>{
return window.requestAnimationFrame ||10
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame
}
class Dot{
constructor(centerX,centerY,centerZ,radius){
// 粒子目的地位置 //保存原来的位置
this.dx = centerX;
this.dy = centerY;
this.dz = centerZ;
//保存粒子聚合后又飞散开的位置
this.tx = 0;
this.ty = 0;
this.tz = 0;
// 粒子现在的位置
this.x = centerX;
this.y = centerY;
this.z = centerZ;
this.radius = radius;
}
print(focallength){
let r = (33 * (Math.random().toFixed(2)/2)).toFixed(0)
let g = (125 * Math.random().toFixed(2)).toFixed(0)
let b = (198 * Math.random().toFixed(2)).toFixed(0)
c.save();
c.beginPath();
let scale = focallength/(focallength + this.z);
let x = canvas.obj.width/2 + (this.x - canvas.obj.width/2) * scale;
let y = canvas.obj.height/2 + (this.y - canvas.obj.height/2) * scale;
c.arc(x,y,this.radius*scale,0,Math.PI * 2);
c.fillStyle = `rgba(33,125,198,${scale})`;
c.fill()
c.restore();
}
}
class ParticleText{
constructor(){
// 焦距
this.focallength = 250;
this.dots = [];
this.pause = false;
this.derection = true;
this.count = 1;
}
init(){
// 取得文字信息
this.dots = this.getImgData("人人FED");
let dots = this.dots;
// 绘制随机点
dots.forEach((item)=>{
item.x = Math.random() * canvas.obj.width;
item.y = Math.random() * canvas.obj.height;
item.z = Math.random() * this.focallength * 2 - this.focallength;
item.tx = Math.random() * canvas.obj.width;
item.ty = Math.random() * canvas.obj.height;
item.tz = Math.random() * this.focallength * 2 - this.focallength;
item.print();
})
// 动画模拟
this.animate()
}
animate(){
c.clearRect(0,0,canvas.obj.width,canvas.obj.height);
this.dots.forEach((item)=>{
// 判断飞散的粒子是否回到了原位
let flag = Math.abs(item.dx - item.x) < 0.1 && Math.abs(item.dy - item.y) < 0.1 && Math.abs(item.dz - item.z) < 0.1;
// let flag = item.dx === item.x && item.dy === item.y && item.dz === item.z;
// 粒子是否回到了飞散后的位置
let flagSecond = Math.abs(item.tx - item.x) < 0.1 && Math.abs(item.ty - item.y)<0.1 && Math.abs(item.tz - item.z) < 0.1;
const speed = 0.1;
if(this.derection){
if(flag){
// 粒子回到了目的地 准备返回
item.x = item.dy;
item.y = item.dy;
item.z = item.dz;
this.derection = false;
}else {
// 粒子没到目的地呢,继续走
// 控制行进的坐标, 0.5是速度系数
item.x = item.x + (item.dx -item.x) * speed;
item.y = item.y + (item.dy -item.y) * speed;
item.z = item.z + (item.dz -item.z) * speed;
}
}else{
// 飞回到了飞散后的位置
if(flagSecond){
item.x = item.tx;
item.y = item.ty;
item.z = item.tz;
this.derection = true;
} else {
// 没飞到,继续飞
item.x = item.x + (item.tx -item.x) * speed;
item.y = item.y + (item.ty -item.y) * speed;
item.z = item.z + (item.tz -item.z) * speed;
// this.pause = false
}
}
item.print(this.focallength);
})
window.requestAnimationFrame(this.animate.bind(this));
}
getImgData(text){
this.drawText(text);
let imgData = c.getImageData(0,0,canvas.obj.width,canvas.obj.height)
// 清楚画布,因为要抹去文字,开始粒子化
c.clearRect(0,0,canvas.obj.width,canvas.obj.height)
let dots = [];
let baseNum =5;
// 在所有的点中,取1/6 之后筛选出符合要求的点
for( let i = 0;i < imgData.width;i += baseNum){
for(let j = 0;j < imgData.height; j += baseNum){
// 取得img中的a
let a = ((j) * imgData.width + i) * 4;
if(imgData.data[a] >= 128){
let dot = new Dot(i-3,j-3,0,2);
dots.push(dot);
}
}
}
return dots;
}
drawText(text){
c.save();
c.font = "250px 微软雅黑 bold";
c.fillStyle = "rgba(168,168,168,1)";
c.textAlign = "center";
c.textBaseline = "middle";
c.fillText(text, canvas.obj.width/2,canvas.obj.height/2);
c.restore();
}
}
let Par = new ParticleText();
Par.init();