[关闭]
@YuriTu 2016-09-20T09:35:35.000000Z 字数 6134 阅读 808

简单的粒子动画的实现思路(一)

canvas


一直想给FED的网站做个好看的首页引导图,看到,webQQ JX框架的这种效果很酷炫,决定模仿一个。

demo:粒子动画(一)demo

粒子动画的基本绘画思路

我们知道canvas之所以强大的原因是它像素级的操作,在画布中的每一个像素都能被我们所操作,而之所以能被操作的原因就是 ImageData对象,这个对象保存着所有的像素数据。

ImageData对象

canvas元素的context中有一个 getImage的api可以拿到当前画布的ImageData,
他的结果:
image

其中的data是一个一维数据,存放着整个二维平面的数据
此处输入图片的描述
类似这样的结构,每4个index,组成一个rgba的数据来描述像素点,所以就构成了如下的数据结构

  1. data = [r,g,b,a,r,b,g,a,........]

和我们在css中使用的rgba不同,其4个数字的有效范围都是0-255,a也是。

绘制的思路

一个关键的思路就是,这个动画要逆向去构建,一堆散乱的粒子拼成特定字符是不好做的,但是把一个图片、文字随机打散到画布就不难了。

3D效果也做好后,就做动画,随机出一个坐标,让粒子先呆在那个坐标,然后通过保存的位置再聚合,然后再随机出坐标再分散,就是这个动画的原理了。

1.取得文字信息
2.创造粒子
3. 重排粒子

  1. // 取得文字信息
  2. this.dots = this.getImgData("人人FED");
  3. let dots = this.dots;
  4. // 绘制随机点
  5. dots.forEach((item)=>{
  6. //告诉各个粒子现在应该所在的位置
  7. item.x = Math.random() * canvas.obj.width;
  8. item.y = Math.random() * canvas.obj.height;
  9. // 焦距,利用二维方式模拟三维效果
  10. item.z = Math.random() * this.focallength * 2 - this.focallength;
  11. //报存粒子飞散后的位置
  12. item.tx = Math.random() * canvas.obj.width;
  13. item.ty = Math.random() * canvas.obj.height;
  14. item.tz = Math.random() * this.focallength * 2 - this.focallength;
  15. //对粒子进行绘制
  16. item.print();
  17. })
  18. // 动画模拟
  19. this.animate()

取得文字信息

首先取得文字信息

  1. getImgData(text){
  2. //使用canvas的api绘制了一个字符在画布上
  3. this.drawText(text);
  4. let imgData = c.getImageData(0,0,canvas.obj.width,canvas.obj.height)
  5. // 清楚画布,因为要抹去文字,开始粒子化
  6. c.clearRect(0,0,canvas.obj.width,canvas.obj.height)
  7. //用于存储所有的粒子
  8. let dots = [];
  9. //粒子数量的比值 1->40k+ 5 ->1.4k+
  10. let baseNum =5;
  11. // 在所有的点中,取1/5 之后筛选出符合要求的点
  12. //可以理解长i为图片某像素的x坐标,j为y坐标
  13. for( let i = 0;i < imgData.width;i += baseNum){
  14. for(let j = 0;j < imgData.height; j += baseNum){
  15. // 记得我们上图的那个excel表格吗?
  16. //取得img中的a 别忘了我们从0开始所以在加一个x坐标
  17. let a = (j * imgData.width + i) * 4;
  18. //只要透明度>0.5 都是文字的组成像素
  19. if(imgData.data[a] >= 128){
  20. //创建粒子,并告诉他,他该移动的终点的xyz坐标与小粒子的radius
  21. let dot = new Dot(i,j,0,2);
  22. dots.push(dot);
  23. }
  24. }
  25. }
  26. return dots;
  27. }

二维数组查找的思路如下
datalist

动画的绘制

Dot对象中保存着3对xyz坐标,分别是 粒子目的地位置、飞散后的位置、当前位置

  1. constructor(centerX,centerY,centerZ,radius){
  2. // 粒子目的地位置 即字的位置
  3. this.dx = centerX;
  4. this.dy = centerY;
  5. this.dz = centerZ;
  6. //保存粒子聚合后又飞散开的位置
  7. this.tx = 0;
  8. this.ty = 0;
  9. this.tz = 0;
  10. // 粒子现在的位置
  11. this.x = centerX;
  12. this.y = centerY;
  13. this.z = centerZ;
  14. this.radius = radius;
  15. }

接下来就该让我们的像素动起来了

  1. animate(){
  2. //每次开始动画时清除前面的画面
  3. c.clearRect(0,0,canvas.obj.width,canvas.obj.height);
  4. //dots保存着所有的粒子
  5. this.dots.forEach((item)=>{
  6. // 判断飞散的粒子是否回到了原位 判断与目标位置的差距小于0.1,即为约达到了原位置;因为涉及小数对比没法直接用 ===
  7. 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;
  8. // 粒子是否回到了飞散后的位置
  9. 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;
  10. const speed = 0.1;
  11. if(this.derection){
  12. if(flag){
  13. // 粒子回到了目的地 准备返回
  14. item.x = item.dy;
  15. item.y = item.dy;
  16. item.z = item.dz;
  17. //到达目的地反向
  18. this.derection = false;
  19. }else {
  20. // 粒子没到目的地呢,继续走
  21. // 控制行进的坐标
  22. item.x = item.x + (item.dx -item.x) * speed;
  23. item.y = item.y + (item.dy -item.y) * speed;
  24. item.z = item.z + (item.dz -item.z) * speed;
  25. }
  26. }else{
  27. // 飞回到了飞散后的位置
  28. if(flagSecond){
  29. item.x = item.tx;
  30. item.y = item.ty;
  31. item.z = item.tz;
  32. this.derection = true;
  33. } else {
  34. // 没飞到,继续飞
  35. item.x = item.x + (item.tx -item.x) * speed;
  36. item.y = item.y + (item.ty -item.y) * speed;
  37. item.z = item.z + (item.tz -item.z) * speed;
  38. }
  39. }
  40. //每个粒子的重新渲染
  41. item.print(this.focallength);
  42. })
  43. window.requestAnimationFrame(this.animate.bind(this));
  44. }

至此我们的动画效果就完成了~100行多点,动手试试吧~

参考资料:

1.打造高大上的Canvas粒子动画
2.事情没有想象中那么难
3.随便谈谈用canvas来实现文字图片粒子化

源码:

  1. let canvas = {};
  2. canvas.obj = document.getElementById("world");
  3. canvas.ctx = canvas.obj.getContext("2d")
  4. let c = canvas.obj.getContext("2d")
  5. window.requestNextAnimationFrame = () =>{
  6. return window.requestAnimationFrame ||10
  7. window.webkitRequestAnimationFrame ||
  8. window.mozRequestAnimationFrame ||
  9. window.msRequestAnimationFrame
  10. }
  11. class Dot{
  12. constructor(centerX,centerY,centerZ,radius){
  13. // 粒子目的地位置 //保存原来的位置
  14. this.dx = centerX;
  15. this.dy = centerY;
  16. this.dz = centerZ;
  17. //保存粒子聚合后又飞散开的位置
  18. this.tx = 0;
  19. this.ty = 0;
  20. this.tz = 0;
  21. // 粒子现在的位置
  22. this.x = centerX;
  23. this.y = centerY;
  24. this.z = centerZ;
  25. this.radius = radius;
  26. }
  27. print(focallength){
  28. let r = (33 * (Math.random().toFixed(2)/2)).toFixed(0)
  29. let g = (125 * Math.random().toFixed(2)).toFixed(0)
  30. let b = (198 * Math.random().toFixed(2)).toFixed(0)
  31. c.save();
  32. c.beginPath();
  33. let scale = focallength/(focallength + this.z);
  34. let x = canvas.obj.width/2 + (this.x - canvas.obj.width/2) * scale;
  35. let y = canvas.obj.height/2 + (this.y - canvas.obj.height/2) * scale;
  36. c.arc(x,y,this.radius*scale,0,Math.PI * 2);
  37. c.fillStyle = `rgba(33,125,198,${scale})`;
  38. c.fill()
  39. c.restore();
  40. }
  41. }
  42. class ParticleText{
  43. constructor(){
  44. // 焦距
  45. this.focallength = 250;
  46. this.dots = [];
  47. this.pause = false;
  48. this.derection = true;
  49. this.count = 1;
  50. }
  51. init(){
  52. // 取得文字信息
  53. this.dots = this.getImgData("人人FED");
  54. let dots = this.dots;
  55. // 绘制随机点
  56. dots.forEach((item)=>{
  57. item.x = Math.random() * canvas.obj.width;
  58. item.y = Math.random() * canvas.obj.height;
  59. item.z = Math.random() * this.focallength * 2 - this.focallength;
  60. item.tx = Math.random() * canvas.obj.width;
  61. item.ty = Math.random() * canvas.obj.height;
  62. item.tz = Math.random() * this.focallength * 2 - this.focallength;
  63. item.print();
  64. })
  65. // 动画模拟
  66. this.animate()
  67. }
  68. animate(){
  69. c.clearRect(0,0,canvas.obj.width,canvas.obj.height);
  70. this.dots.forEach((item)=>{
  71. // 判断飞散的粒子是否回到了原位
  72. 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;
  73. // let flag = item.dx === item.x && item.dy === item.y && item.dz === item.z;
  74. // 粒子是否回到了飞散后的位置
  75. 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;
  76. const speed = 0.1;
  77. if(this.derection){
  78. if(flag){
  79. // 粒子回到了目的地 准备返回
  80. item.x = item.dy;
  81. item.y = item.dy;
  82. item.z = item.dz;
  83. this.derection = false;
  84. }else {
  85. // 粒子没到目的地呢,继续走
  86. // 控制行进的坐标, 0.5是速度系数
  87. item.x = item.x + (item.dx -item.x) * speed;
  88. item.y = item.y + (item.dy -item.y) * speed;
  89. item.z = item.z + (item.dz -item.z) * speed;
  90. }
  91. }else{
  92. // 飞回到了飞散后的位置
  93. if(flagSecond){
  94. item.x = item.tx;
  95. item.y = item.ty;
  96. item.z = item.tz;
  97. this.derection = true;
  98. } else {
  99. // 没飞到,继续飞
  100. item.x = item.x + (item.tx -item.x) * speed;
  101. item.y = item.y + (item.ty -item.y) * speed;
  102. item.z = item.z + (item.tz -item.z) * speed;
  103. // this.pause = false
  104. }
  105. }
  106. item.print(this.focallength);
  107. })
  108. window.requestAnimationFrame(this.animate.bind(this));
  109. }
  110. getImgData(text){
  111. this.drawText(text);
  112. let imgData = c.getImageData(0,0,canvas.obj.width,canvas.obj.height)
  113. // 清楚画布,因为要抹去文字,开始粒子化
  114. c.clearRect(0,0,canvas.obj.width,canvas.obj.height)
  115. let dots = [];
  116. let baseNum =5;
  117. // 在所有的点中,取1/6 之后筛选出符合要求的点
  118. for( let i = 0;i < imgData.width;i += baseNum){
  119. for(let j = 0;j < imgData.height; j += baseNum){
  120. // 取得img中的a
  121. let a = ((j) * imgData.width + i) * 4;
  122. if(imgData.data[a] >= 128){
  123. let dot = new Dot(i-3,j-3,0,2);
  124. dots.push(dot);
  125. }
  126. }
  127. }
  128. return dots;
  129. }
  130. drawText(text){
  131. c.save();
  132. c.font = "250px 微软雅黑 bold";
  133. c.fillStyle = "rgba(168,168,168,1)";
  134. c.textAlign = "center";
  135. c.textBaseline = "middle";
  136. c.fillText(text, canvas.obj.width/2,canvas.obj.height/2);
  137. c.restore();
  138. }
  139. }
  140. let Par = new ParticleText();
  141. Par.init();

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注