Canvas JavaScript 制作图片粒子效果分分快三计划

作者:分分快三计划

前面的话

  本文将从最基本的imageData对象的理论知识说开去,详细介绍canvas粒子系统的构建

 

      var pos =[( i-1 )*200] ( j-1 )]*4;

打造高大上的Canvas粒子动画

2016/08/22 · HTML5 · 5 评论 · Canvas

原文出处: 腾讯ISUX   

首先来看下我们准备要做的粒子动画效果是怎么样的~

是这样:

分分快三计划 1

或者是这样:

分分快三计划 2

甚至是这样:

分分快三计划 3

很酷炫!

那如何去实现类似上面的粒子动画甚至根据自己的喜好去做更多其他轨迹的动画呢~请看下面详细的讲解。

imageData

  关于图像数据imageData共有3个方法,包括getImageData()、putImageData()、createImageData()

【getImageData()】

  2D上下文可以通过getImageData()取得原始图像数据。这个方法接收4个参数:画面区域的x和y坐标以及该区域的像素宽度和高度

  例如,要取得左上角坐标为(10,5)、大小为50*50像素的区域的图像数据,可以使用以下代码:

var imageData = context.getImageData(10,5,50,50);

  返回的对象是ImageData的实例,每个ImageData对象有3个属性:widthheightdata

  1、width:表示imageData对角的宽度

  2、height:表示imageData对象的高度

  3、data是一个数组,保存着图像中每一个像素的数据。在data数组中,每一个像素用4个元素来保存,分别表示red、green、blue、透明度

  [注意]图像中有多少像素,data的长度就等于像素个数乘以4

分分快三计划 4

//第一个像素如下
var data = imageData.data;
var red = data[0];
var green = data[1]; 
var blue = data[2];
var alpha = data[3];

分分快三计划 5

  数组中每个元素的值是在0-255之间,能够直接访问到原始图像数据,就能够以各种方式来操作这些数据

  [注意]如果要使用getImageData()获取的canvas中包含drawImage()方法,则该方法中的URL不能跨域

【createImageData()】

  createImageData(width,height)方法创建新的空白ImageData对象。新对象的默认像素值 transparent black,相当于rgba(0,0,0,0)

var imgData = context.createImageData(100,100);

【putImageData()】

  putImageData()方法将图像数据从指定的ImageData对象放回画布上,该方法共有以下参数

分分快三计划 6

imgData:要放回画布的ImageData对象(必须)
x:imageData对象的左上角的x坐标(必须)
y:imageData对象的左上角的y坐标(必须)
dirtyX:在画布上放置图像的水平位置(可选)
dirtyY:在画布上放置图像的垂直位置(可选)
dirtyWidth:在画布上绘制图像所使用的宽度(可选)
dirtyHeight:在画布上绘制图像所使用的高度(可选)

分分快三计划 7

  [注意]参数3到7要么都没有,要么都存在

context.putImageData(imgData,0,0);    
context.putImageData(imgData,0,0,50,50,200,200);

 

  以上图为例。图像的宽和高都为200,如果按照每一个像素为一行一列时。则该图像共有200行,200列。所以要取得 i 行第 j 列的像素初始位置信息为:

技术选择

因为粒子数量很多,而且涉及到图像像素处理,所以这里使用Canvas是不二选择。

 

注意,以下演示的代码只是关键代码,重点在于解决思路。

效果演示

  下面是实例效果演示,博文结尾有全部源码

 

可惜本人数学不好,算法不会。不能为粒子加上炫酷的动态效果~~其实可以找一些算法让粒子动起来的,有兴趣可以做做看~

3. 获取图像的像素信息,并根据像素信息重新绘制出粒子效果轮廓图

canvas有一个叫getImageData的接口,通过该接口可以获取到画布上指定位置的全部像素的数据:

分分快三计划 8

把获取的imageData输出到控制台可以看到,imageData包含三个属性:

分分快三计划 9

其中,width、height是读取图像像素信息完整区域的宽度和高度,data是一个Uint8ClampedArray类型的一维数组,包含了整个图片区域里每个像素点的RGBA的整型数据。这里必须要理解这个数组所保存像素信息的排序规则,请看下图描述的data数组:

分分快三计划 10

每一个色值占据data数组索引的一个位置,一个像素有个4个值(R、G、B、A)占据数组的4个索引位置。根据数列规则可以知道,要获取第n个位置(n从1开始)的R、G、B像素信息就是:Rn = (n-1)*4 ,Gn = (n-1)*4 1 ,Bn = (n-1)*4 2  ,so easy~  当然,实际上图像是一个包括image.height行,image.width列像素的矩形而不是单纯的一行到结束的,这个n值在矩形中要计算下:

分分快三计划 11

由于一个像素是带有4个索引值(rgba)的,所以拿到图像中第i行第j列的R、G、B、A像素信息就是 Rij = [(j-1)*imageData.width (i-1)]*4 ,Gij = [(j-1)*imageData.width (i-1)]*4 1,Bij = [(j-1)*imageData.width (i-1)]*4 2,Aij = [(j-1)*imageData.width (i-1)]*4 3 。每个像素值都可以拿到了!

接下来就要把图像的粒子化轮廓图画出来了。那么,怎么做这个轮廓图呢,我们先读取每个像素的信息(用到上面的计算公式),如果这个像素的色值符合要求,就保存起来,用于绘制在画布上。另外,既然是做成粒子的效果,我们只需要把像素粒子保存一部分,展示在画布上。

具体做法是,设定每一行和每一列要显示的粒子数,分别是cols和rows,一个粒子代表一个单元格,那么每个单元格的的宽高就是imageWidth/cols和imageHeight/rows,然后循环的判断每个单元格的第一个像素是否满足像素值的条件,如果满足了,就把这个单元格的坐标保存到数组里,用作后续绘制图案用。

关键参考代码:

分分快三计划 12

用完整代码做出的demo及效果:

分分快三计划 13

至此,粒子轮廓图已经制作完成。

 

粒子筛选

  当粒子完全写入时,与canvas复制粘贴的效果相同。而当粒子有所筛选时,则会出现一些奇妙的效果

【按序筛选】

  由于取得粒子时,使用的是宽度值*高度值的双重循环,且都以加1的形式递增。如果不是加1,而是加n,则可以实现按序筛选的效果

<canvas id="drawing1" style="border:1px solid black"></canvas>
<canvas id="drawing2" style="border:1px solid black"></canvas>
<div id="con">
  <button>1</button>
  <button>2</button>
  <button>3</button>
  <button>4</button>
  <button>5</button>
</div>
<script>
var oCon = document.getElementById('con');
oCon.onclick = function(e){
  e = e || event;
  var tempN = e.target.innerHTML;
  if(tempN){
    cxt2.clearRect(0,0,W,H);
    cxt2.putImageData(setData(imageData,Number(tempN)),0,0);
  }
}
var drawing1 = document.getElementById('drawing1');
var drawing2 = document.getElementById('drawing2');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var cxt2 = drawing2.getContext('2d');
  var W = drawing1.width = drawing2.width = 200;
  var H = drawing1.height = drawing2.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh   'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  //写入drawing2中 
  cxt2.putImageData(setData(imageData,1),0,0);
  function setData(imageData,n){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    for(var i = 0; i < W; i =n){
      for(var j = 0; j < H ;j =n){
        //data值中的红色值
        var k = 4*(i   j*W);
        //data值中的透明度
        if(imageData.data[k 3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
        }
      }
    }
    //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中
    var oNewImage = cxt.createImageData(W,H);
    for(var i = 0; i < dots.length; i  ){
      oNewImage.data[dots[i] 0] = imageData.data[dots[i] 0];
      oNewImage.data[dots[i] 1] = imageData.data[dots[i] 1];
      oNewImage.data[dots[i] 2] = imageData.data[dots[i] 2];
      oNewImage.data[dots[i] 3] = imageData.data[dots[i] 3];
    }
    return oNewImage;
  }
}
</script>  

  点击下面的不同按钮,可以得到不同程度的粒子筛选

【随机筛选】

  除了使用按序筛选,还可以使用随机筛选。 通过双重循环得到的粒子的位置信息,放到dots数组中。通过splice()方法进行筛选,将筛选后的位置信息放到新建的newDots数组中,然后再使用createImageData(),新建一个图像数据对象并返回

<canvas id="drawing1" style="border:1px solid black"></canvas>
<canvas id="drawing2" style="border:1px solid black"></canvas>
<div id="con">
  <button>1000</button>
  <button>2000</button>
  <button>3000</button>
  <button>4000</button>
</div>
<script>
var oCon = document.getElementById('con');
oCon.onclick = function(e){
  e = e || event;
  var tempN = e.target.innerHTML;
  if(tempN){
    cxt2.clearRect(0,0,W,H);
    cxt2.putImageData(setData(imageData,1,Number(tempN)),0,0);
  }
}
var drawing1 = document.getElementById('drawing1');
var drawing2 = document.getElementById('drawing2');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var cxt2 = drawing2.getContext('2d');
  var W = drawing1.width = drawing2.width = 200;
  var H = drawing1.height = drawing2.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh   'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  //写入drawing2中 
  cxt2.putImageData(setData(imageData,1),0,0);
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    for(var i = 0; i < W; i =n){
      for(var j = 0; j < H ;j =n){
        //data值中的红色值
        var k = 4*(i   j*W);
        //data值中的透明度
        if(imageData.data[k 3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
        }
      }
    }    
    //筛选粒子,仅保存m个到newDots数组中。如果不传入m,则不进行筛选
    var newDots = [];
    if(m && (dots.length > m)){
      for(var i = 0; i < m; i  ){
        newDots.push(Number(dots.splice(Math.floor(Math.random()*dots.length),1)));
      }
    }else{
      newDots = dots;
    }    
    //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中
    var oNewImage = cxt.createImageData(W,H);
    for(var i = 0; i < newDots.length; i  ){
      oNewImage.data[newDots[i] 0] = imageData.data[newDots[i] 0];
      oNewImage.data[newDots[i] 1] = imageData.data[newDots[i] 1];
      oNewImage.data[newDots[i] 2] = imageData.data[newDots[i] 2];
      oNewImage.data[newDots[i] 3] = imageData.data[newDots[i] 3];
    }
    return oNewImage;
  }
}
</script>  

  点击下面的不同按钮,可以筛选出对应数量的随机的粒子

 

2:了解像素区域数据的排布说明,以上获取的图片数据像素信息(imageData对象中的data属性)为RGBA整型的一维数组数据。一个像素是有4个值(R,G,B,A)组成的。也就是说,数组信息每四个为一个像素点。因此,有以下规则,

本文部分图片引用来自

  • https://zh.wikipedia.org

    3 赞 12 收藏 5 评论

分分快三计划 14

鼠标交互

  一般地,粒子的鼠标交互都与isPointInPath(x,y)方法有关

【移入变色】

  当鼠标接近粒子时,该粒子变红。实现原理很简单。鼠标移动时,通过isPointInPath(x,y)方法检测,有哪些粒子处于当前指针范围内。如果处于,绘制1像素的红色矩形即可

<canvas id="drawing1" style="border:1px solid black"></canvas>
<script>
var drawing1 = document.getElementById('drawing1');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 200;
  var H = drawing1.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh   'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    //dots的索引
    var index = 0;
    for(var i = 0; i < W; i =n){
      for(var j = 0; j < H ;j =n){
        //data值中的红色值
        var k = 4*(i   j*W);
        //data值中的透明度
        if(imageData.data[k 3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
          dots[index  ] = {
            'index':index,
            'x':i,
            'y':j,
            'red':k,
            'randomX':Math.random()*W,
            'randomY':Math.random()*H,
          }
        }
      }
    }    
    //筛选粒子,仅保存dots.length/m个到newDots数组中
    var newDots = [];
    var len = Math.floor(dots.length/m);
    for(var i = 0; i < len; i  ){
      newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]);
    }
    return newDots;
  }
  //获得粒子数组
  var dataArr = setData(imageData,1,1);  
  //鼠标移动时,当粒子距离鼠标指针小于10时,则进行相关操作
  drawing1.onmousemove = function(e){
    e = e || event;
    var x = e.clientX - drawing1.getBoundingClientRect().left;
    var y = e.clientY - drawing1.getBoundingClientRect().top;
    cxt.beginPath();
    cxt.arc(x,y,10,0,Math.PI*2);
    for(var i = 0; i < dataArr.length; i  ){
      var temp = dataArr[i];
      if(cxt.isPointInPath(temp.x,temp.y)){   
        cxt.fillStyle = 'red';
        cxt.fillRect(temp.x,temp.y,1,1);
      }        
    }   
  }
}
</script> 

  鼠标靠近文字时,可将靠近文字的区域变成红色

 【远离鼠标】

  鼠标点击时,以鼠标指针为圆心的一定范围内的粒子需要移动到该范围以外。一段时间后,粒子回到原始位置

  实现原理并不复杂,使用isPointInPath(x,y)方法即可,如果粒子处于当前路径中,则沿着鼠标指针与粒子坐标组成的直线方向,移动到路径的边缘

<canvas id="drawing1" style="border:1px solid black"></canvas>
<script>
var drawing1 = document.getElementById('drawing1');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 200;
  var H = drawing1.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh   'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  //渲染文字
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  cxt.clearRect(0,0,W,H);
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    //dots的索引
    var index = 0;
    for(var i = 0; i < W; i =n){
      for(var j = 0; j < H ;j =n){
        //data值中的红色值
        var k = 4*(i   j*W);
        //data值中的透明度
        if(imageData.data[k 3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
          dots[index  ] = {
            'index':index,
            'x':i,
            'y':j,
            'red':k,
            'randomX':Math.random()*W,
            'randomY':Math.random()*H,
            'mark':false
          }
        }
      }
    }    
    //筛选粒子,仅保存dots.length/m个到newDots数组中
    var newDots = [];
    var len = Math.floor(dots.length/m);
    for(var i = 0; i < len; i  ){
      newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]);
    }
    return newDots;
  }
  //获得粒子数组
  var dataArr = setData(imageData,2,1); 
  //将筛选后的粒子信息保存到新建的imageData中
  var oNewImage = cxt.createImageData(W,H);
  for(var i = 0; i < dataArr.length; i  ){
    for(var j = 0; j < 4; j  ){
      oNewImage.data[dataArr[i].red j] = imageData.data[dataArr[i].red j];
    }
  }    
  //写入canvas中
  cxt.putImageData(oNewImage,0,0);
  //设置鼠标检测半径为r
  var r = 20;
  //鼠标移动时,当粒子距离鼠标指针小于20时,则进行相关操作
  drawing1.onmousedown = function(e){
    e = e || event;
    var x = e.clientX - drawing1.getBoundingClientRect().left;
    var y = e.clientY - drawing1.getBoundingClientRect().top;
    cxt.beginPath();
    cxt.arc(x,y,r,0,Math.PI*2);
    for(var i = 0; i < dataArr.length; i  ){
      var temp = dataArr[i];
      if(cxt.isPointInPath(temp.x,temp.y)){  
        temp.mark = true;
        var angle = Math.atan2((temp.y - y),(temp.x - x));
        temp.endX =  x - r*Math.cos(angle);
        temp.endY =  y - r*Math.sin(angle);
        var disX = temp.x - temp.endX;
        var disY = temp.y - temp.endY;
        cxt.fillStyle = '#fff';
        cxt.fillRect(temp.x,temp.y,1,1);
        cxt.fillStyle = '#000';
        cxt.fillRect(temp.endX,temp.endY,1,1);  
        dataRecovery(10);
      }else{
        temp.mark = false;
      }     
    }
    var oTimer = null;
    function dataRecovery(n){
      clearTimeout(oTimer);
      oTimer = setTimeout(function(){
        cxt.clearRect(0,0,W,H);
        for(var i = 0; i < dataArr.length; i  ){
          var temp = dataArr[i];
          if(temp.mark){
            var x0 = temp.endX;
            var y0 = temp.endY;
            var disX = temp.x - x0;
            var disY = temp.y - y0;
            cxt.fillRect(x0   disX/n,y0   disY/n,1,1);  
          }else{
            cxt.fillRect(temp.x,temp.y,1,1);
          }
        }   
        dataRecovery(n-1);    
        if(n === 1){
          clearTimeout(oTimer);
        }      
      },17);
    } 
  }  
}
</script>  

  使用鼠标点击canvas中的文字,出现效果

 

 

粒子执行动画的时机

要让粒子效果比较明显,那就不能让动画效果执行太过整体了,需要让图案上每个粒子有不同的时间间隔启动,根据一定的规律交错的执行动画。这里的粒子启动间隔有两种,一种是每一行粒子执行时间间隔,要让每一行的粒子启动时间有规律错开;另外一种是每一行粒子之间启动时间随机的错开,这样执行的粒子动画才会有一种层次感和每个粒子有独立动画的颗粒感。看下加了粒子启动时间间隔之后的效果对比:

分分快三计划 15

比上面不加粒子启动时间间隔的效果好多了。

 

嗯,介绍差不多就是这样了,如果上面介绍的方法还是解决不了问题的话,还有办法。。。我把粒子动画效果和Tween的缓动函数一起封装了一下。直接配置一下就可以用了。 用法就是创建一个带有id的canvas,设定好宽度和高度,引入particle.min.js,然后配置一下参数即可, demo:

分分快三计划 16

只有canvasId、imgUrl、cols、rows是必填的,其他参数都是根据需要自己选填。  ( ͡° ͜ʖ ͡°)✧

 

综合实例

  下面将上面的效果制作为一个可编辑的综合实例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
<canvas id="drawing1" style="border:1px solid black"></canvas>
<br>
<div style="margin-bottom:10px">
  粒子设置:
  <input type="text" id="textValue" value="小火柴的蓝色理想">  
  <button id="btnSetText">文字设置确认</button>
  <button id="btnchoose2">按序筛选</button>
  <button id="btnchoose3">随机筛选</button>
  <button id="btnchoose1">不筛选</button>  
</div>
<div style="margin-bottom:10px">
  粒子效果:
  <button id="btn1">按序显字</button>
  <button id="btn2">随机显字</button>  
  <button id="btn3">混乱聚合</button>
  <button id="btn4">重新混乱</button>
</div>
<div>
  鼠标效果:
  1、鼠标移到文字上时,文字颜色变红;
  2、鼠标在文字上点击时,粒子远离鼠标指针
</div>
<script>
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 300;
  var H = drawing1.height = 200; 
  var imageData;
  var dataArr;
  btnSetText.onclick = function(){
    fnSetText(textValue.value);
  }  
  function fnSetText(str){
    cxt.clearRect(0,0,W,H);
    cxt.textBaseline = 'top';
    var sh = 60;
    cxt.font = sh   'px  宋体'
    var sw = cxt.measureText(str).width;
    if(sw > W){
        sw = W;
    }
    cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);  
    imageData = cxt.getImageData(0,0,W,H); 
    dataArr = setData(imageData,1,1); 
  }
  fnSetText('小火柴');
  btnchoose1.onclick = function(){
    dataArr = setData(imageData,1,1);
    saveData(dataArr);  
  }
  btnchoose2.onclick = function(){
    dataArr = setData(imageData,2,1);
    saveData(dataArr); 
  }
  btnchoose3.onclick = function(){
    dataArr = setData(imageData,1,2);
    saveData(dataArr); 
  }    
  //筛选粒子
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    //dots的索引
    var index = 0;
    for(var i = 0; i < W; i =n){
      for(var j = 0; j < H ;j =n){
        //data值中的红色值
        var k = 4*(i   j*W);
        //data值中的透明度
        if(imageData.data[k 3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
          dots[index  ] = {
            'index':index,
            'x':i,
            'y':j,
            'red':k,
            'green':k 1,
            'blue':k 2,
            'randomX':Math.random()*W,
            'randomY':Math.random()*H,
            'mark':false
          }
        }
      }
    }    
    //筛选粒子,仅保存dots.length/m个到newDots数组中
    var newDots = [];
    var len = Math.floor(dots.length/m);
    for(var i = 0; i < len; i  ){
      newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]);
    }
    return newDots;
  }
  function saveData(dataArr){
    //将筛选后的粒子信息保存到新建的imageData中
    var oNewImage = cxt.createImageData(W,H);
    for(var i = 0; i < dataArr.length; i  ){
      for(var j = 0; j < 4; j  ){
        oNewImage.data[dataArr[i].red j] = imageData.data[dataArr[i].red j];
      }
    }
    //写入canvas中
    cxt.putImageData(oNewImage,0,0);       
  }
  //显示粒子
  function showData(arr,oTimer,index,n){
    oTimer = setTimeout(function(){
      cxt.clearRect(0,0,W,H);
      //写入canvas中 
      saveData(arr[index  ]); 
      if(index == n){
        clearTimeout(oTimer);
      }else{
        //迭代函数       
        showData(arr,oTimer,index,n);           
      }                     
    },60);      
  }   
  //重新混乱
  function showDataToRandom(dataArr,oTimer,n){
    oTimer = setTimeout(function fn(){
      cxt.clearRect(0,0,W,H);
      for(var i = 0; i < dataArr.length; i  ){
        var temp = dataArr[i];
        var x0 = temp.x;
        var y0 = temp.y;
        var disX = temp.randomX - temp.x;
        var disY = temp.randomY - temp.y;
        cxt.fillRect(x0   disX/n,y0   disY/n,1,1);             
      } 
      n--;
      if(n === 0){
        clearTimeout(oTimer);
      }else{
        showDataToRandom(dataArr,oTimer,n); 
      }             
    },60);  
  } 
  //混乱聚合
  function showRandomToData(dataArr,oTimer,n){
    oTimer = setTimeout(function(){
      cxt.clearRect(0,0,W,H);
      for(var i = 0; i < dataArr.length; i  ){
        var temp = dataArr[i];
        var x0 = temp.randomX;
        var y0 = temp.randomY;
        var disX = temp.x - temp.randomX;
        var disY = temp.y - temp.randomY;
        cxt.fillRect(x0   disX/n,y0   disY/n,1,1);  
      }   
      n--;
      if(n === 0){
        clearTimeout(oTimer);
      }else{
        showRandomToData(dataArr,oTimer,n); 
      }      
    },60);  
  }
  btn1.onclick = function(){
    btn1.arr = [];
    for(var i = 10; i > 1; i--){
      btn1.arr.push(setData(imageData,i,1));
    }
    showData(btn1.arr,btn1.oTimer,0,9);
  }
  btn2.onclick = function(){
    btn2.arr = [];
    for(var i = 10; i > 0; i--){
      btn2.arr.push(setData(imageData,2,i));
    }
    showData(btn2.arr,btn2.oTimer,0,10);
  }   
  btn3.onclick = function(){
    clearTimeout(btn3.oTimer);
    showRandomToData(dataArr,btn3.oTimer,10);
  }
  btn4.onclick = function(){
    clearTimeout(btn4.oTimer);
    showDataToRandom(dataArr,btn4.oTimer,10);
  }  
  //鼠标移动
  drawing1.onmousemove = function(e){
    e = e || event;
    var x = e.clientX - drawing1.getBoundingClientRect().left;
    var y = e.clientY - drawing1.getBoundingClientRect().top;
    cxt.beginPath();
    cxt.arc(x,y,10,0,Math.PI*2);
    for(var i = 0; i < dataArr.length; i  ){
      var temp = dataArr[i];
      if(cxt.isPointInPath(temp.x,temp.y)){   
        cxt.fillStyle = 'red';
        cxt.fillRect(temp.x,temp.y,1,1);
      }        
    }
    cxt.fillStyle = 'black';   
  }    
  //鼠标点击
  drawing1.onmousedown = function(e){
    var r = 20;
    e = e || event;
    var x = e.clientX - drawing1.getBoundingClientRect().left;
    var y = e.clientY - drawing1.getBoundingClientRect().top;
    cxt.beginPath();
    cxt.arc(x,y,r,0,Math.PI*2);
    for(var i = 0; i < dataArr.length; i  ){
      var temp = dataArr[i];
      if(cxt.isPointInPath(temp.x,temp.y)){  
        temp.mark = true;
        var angle = Math.atan2((temp.y - y),(temp.x - x));
        temp.endX =  x - r*Math.cos(angle);
        temp.endY =  y - r*Math.sin(angle);
        var disX = temp.x - temp.endX;
        var disY = temp.y - temp.endY;
        cxt.fillStyle = '#fff';
        cxt.fillRect(temp.x,temp.y,1,1);
        cxt.fillStyle = '#f00';
        cxt.fillRect(temp.endX,temp.endY,1,1);  
        cxt.fillStyle="#000";
        dataRecovery(10);
      }else{
        temp.mark = false;
      }     
    }
    var oTimer = null;
    function dataRecovery(n){
      clearTimeout(oTimer);
      oTimer = setTimeout(function(){
        cxt.clearRect(0,0,W,H);
        for(var i = 0; i < dataArr.length; i  ){
          var temp = dataArr[i];
          if(temp.mark){
            var x0 = temp.endX;
            var y0 = temp.endY;
            var disX = temp.x - x0;
            var disY = temp.y - y0;
            cxt.fillRect(x0   disX/n,y0   disY/n,1,1);  
          }else{
            cxt.fillRect(temp.x,temp.y,1,1);
          }
        }   
        dataRecovery(n-1);    
        if(n === 1){
          clearTimeout(oTimer);
        }      
      },17);
    } 
  }  
}
</script> 
</body>
</html>

 

  另外,像素区域既然是一个区域,它是有宽和高的。上面的推算公式适合单独一行使用定位一个像素点。所以计算像素点时要考虑到在整个图像区域内定位:

制作缓动效果有两种方法:

一种是自己设定好控制点,然后通过贝塞尔曲线公式来计算每个单位时间的坐标值。

引用了wikipedia里面的图:

分分快三计划 17分分快三计划 18

上面两个图都是在绘制一条特定曲线,可以看出二次曲线需要一个特定控制点P1,三次曲线需要两个特定控制点P1和P2来确定一条曲线,高阶曲线甚至需要更多的控制点来确定曲线轨迹。

求曲线的公式是根据德卡斯特里奥算法计算得来的,直接上公式。

二次曲线对应的公式:

分分快三计划 19

三次曲线对应的公式:

分分快三计划 20

从公式可以看出,只要确定控制点坐标、起始坐标和终点坐标后,就可以确定了一条曲线,然后就可以根据曲线公式求出每个时刻t对应的位置值B(t)。

当然使用这种方法需要自己去制定控制点坐标,计算也比较复杂,实现起来很繁琐。没事,我们还有别的办法确定曲线。

 

另外一种方法就是使用已有的缓动函数,不需要自己制定控制点,这里推荐出名的Tween算法的缓动函数,用其中一个缓动函数来介绍下参数值,其他缓动函数所传的参数值是一样的:

分分快三计划 21

是不是觉得很熟悉?对没错,jquery用的动画扩展插件easing.js就是Tween算法的缓动函数。有了这现成的缓动函数,就可以制定粒子的起始点、终点(终点就是图案本身的坐标位置)以及动画执行持续时间来做我们要的效果。

关键参考代码:

分分快三计划 22

根据参考代码做出一个效果:

分分快三计划 23

嗯,动画效果是有了,但总感觉不太对劲。。。唔,仔细观察一下,是图案动画执行太过整体了,没有明显的颗粒动画效果,这就引出粒子动画的另一个关键点,粒子执行动画的时机。

 

粒子写入

  粒子,指图像数据imageData中的每一个像素点。下面以一个简易实例来说明完全写入与粒子写入

【完全写入】

  200*200的canvas1中存在文字'小火柴',并将canvas1整个作为图像数据写入同样尺寸的canvas2中

<canvas id="drawing1" style="border:1px solid black"></canvas>
<canvas id="drawing2" style="border:1px solid black"></canvas>
<script>
var drawing1 = document.getElementById('drawing1');
var drawing2 = document.getElementById('drawing2');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var cxt2 = drawing2.getContext('2d');
  var W = drawing1.width = drawing2.width = 200;
  var H = drawing1.height = drawing2.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh   'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  //写入drawing2中 
  cxt2.putImageData(imageData,0,0);
</script>  

【粒子写入】

  对于完全写入而言,相当于只是简单的复制粘贴,如果要对每个像素点进行精细地控制,则需要使用粒子写入。canvas1中存在着大量的空白区域,只有'小火柴'这三个字的区域是有效的。于是,可以根据图像数据imageData中的透明度对粒子进行筛选,只筛选出透明度大于0的粒子

<canvas id="drawing1" style="border:1px solid black"></canvas>
<canvas id="drawing2" style="border:1px solid black"></canvas>
<script>
var drawing1 = document.getElementById('drawing1');
var drawing2 = document.getElementById('drawing2');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var cxt2 = drawing2.getContext('2d');
  var W = drawing1.width = drawing2.width = 200;
  var H = drawing1.height = drawing2.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh   'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  //写入drawing2中 
  cxt2.putImageData(setData(imageData),0,0);
  function setData(imageData){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    for(var i = 0; i < W; i  ){
      for(var j = 0; j < H ;j  ){
        //data值中的红色值
        var k = 4*(i   j*W);
        //data值中的透明度
        if(imageData.data[k 3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
        }
      }
    }
    //40000 2336
    console.log(i*j,dots.length);
    //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中
    var oNewImage = cxt.createImageData(W,H);
    for(var i = 0; i < dots.length; i  ){
      oNewImage.data[dots[i] 0] = imageData.data[dots[i] 0];
      oNewImage.data[dots[i] 1] = imageData.data[dots[i] 1];
      oNewImage.data[dots[i] 2] = imageData.data[dots[i] 2];
      oNewImage.data[dots[i] 3] = imageData.data[dots[i] 3];
    }
    return oNewImage;
  }
}
</script>  

  虽然结果看上去相同,但canvas2只使用了canvas1中40000个粒子中的2336个

 

imageData={
    data:Unit8ClampedArray[10000] //一个包含图片区域内每个像素点的RGBA的整型数据信息
    height:200   //读取的图片像素信息区域高度
    width:200   //读取的图片像素信息区域宽度
}

粒子动画轨迹

动画位移的轨迹,最常见的就是单位时间内改变固定的位移值,从而达到动画效果。但要做到炫酷的效果依赖这种单调固定的位移肯定是不行的。所以位移可以依赖缓动函数去做到单位时间内改变不一样的位移值,从而达到特别的效果。

像素显字

  下面来使用粒子筛选来实现一个像素显字的效果。像素显字即从不清晰的效果逐步过渡到完全显示

【按序像素显字】

  按序像素显字的实现原理非常简单,比如,共有2000个粒子,共10个程度的过渡效果。则使用10个数组,分别保存200,400,600,800,100,1200,1400,1600,1800和2000个粒子。然后使用定时器将其逐步显示出来即可

<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn">开始显字</button>
<script>
var drawing1 = document.getElementById('drawing1');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 200;
  var H = drawing1.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh   'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  cxt.clearRect(0,0,W,H);
  //获得10组粒子
  var imageDataArr = [];
  var n = 10;
  var index = 0;
  for(var i = n; i > 0; i--){
    imageDataArr.push(setData(imageData,i));
  }
  var oTimer = null;
  btn.onclick = function(){
    clearTimeout(oTimer);
    showData();
  }
  function showData(){
    oTimer = setTimeout(function(){
      cxt.clearRect(0,0,W,H);
      //写入drawing1中 
      cxt.putImageData(imageDataArr[index  ],0,0); 
      //迭代函数       
      showData();      
      if(index == 10){
       index = 0;
        clearTimeout(oTimer);
      }      

    },100);      
  }    
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    for(var i = 0; i < W; i =n){
      for(var j = 0; j < H ;j =n){
        //data值中的红色值
        var k = 4*(i   j*W);
        //data值中的透明度
        if(imageData.data[k 3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
        }
      }
    }    
    //筛选粒子,仅保存m个到newDots数组中。如果不传入m,则不进行筛选
    var newDots = [];
    if(m && (dots.length > m)){
      for(var i = 0; i < m; i  ){
        newDots.push(Number(dots.splice(Math.floor(Math.random()*dots.length),1)));
      }
    }else{
      newDots = dots;
    }    
    //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中
    var oNewImage = cxt.createImageData(W,H);
    for(var i = 0; i < newDots.length; i  ){
      oNewImage.data[newDots[i] 0] = imageData.data[newDots[i] 0];
      oNewImage.data[newDots[i] 1] = imageData.data[newDots[i] 1];
      oNewImage.data[newDots[i] 2] = imageData.data[newDots[i] 2];
      oNewImage.data[newDots[i] 3] = imageData.data[newDots[i] 3];
    }
    return oNewImage;
  }
}
</script>  

  点击开始显字,即可出现效果

【随机像素显字】

  随机像素显字的原理类似,保存多个不同数量的随机像素的数组即可

<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn">开始显字</button>
<script>
var drawing1 = document.getElementById('drawing1');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 200;
  var H = drawing1.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh   'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  cxt.clearRect(0,0,W,H);
  //获得10组粒子
  var imageDataArr = [];
  var n = 10;
  var index = 0;
  for(var i = n; i > 0; i--){
    imageDataArr.push(setData(imageData,1,i));
  }
  var oTimer = null;
  btn.onclick = function(){
    clearTimeout(oTimer);
    showData();
  }
  function showData(){
    oTimer = setTimeout(function(){
      cxt.clearRect(0,0,W,H);
      //写入drawing1中 
      cxt.putImageData(imageDataArr[index  ],0,0); 
      //迭代函数       
      showData();      
      if(index == 10){
        clearTimeout(oTimer);
        index = 0;
      }      
    },100);      
  }    
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    for(var i = 0; i < W; i =n){
      for(var j = 0; j < H ;j =n){
        //data值中的红色值
        var k = 4*(i   j*W);
        //data值中的透明度
        if(imageData.data[k 3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
        }
      }
    }    
    //筛选粒子,仅保存dots.length/m个到newDots数组中
    var newDots = [];
    var len = Math.floor(dots.length/m);
    for(var i = 0; i < len; i  ){
      newDots.push(Number(dots.splice(Math.floor(Math.random()*dots.length),1)));
    }
    //新建一个imageData,并将筛选后的粒子信息保存到新建的imageData中
    var oNewImage = cxt.createImageData(W,H);
    for(var i = 0; i < newDots.length; i  ){
      oNewImage.data[newDots[i] 0] = imageData.data[newDots[i] 0];
      oNewImage.data[newDots[i] 1] = imageData.data[newDots[i] 1];
      oNewImage.data[newDots[i] 2] = imageData.data[newDots[i] 2];
      oNewImage.data[newDots[i] 3] = imageData.data[newDots[i] 3];
    }
    return oNewImage;
  }
}
</script> 

 

      .....

1. 创建一个<canvas>元素,并获取Canvas画布渲染上下文

分分快三计划 24

< canvas>是一个双标签元素,通过width和height的值来设置画布的大小。至于ctx(画布渲染上下文),可以理解为画布上的画笔,我们可以通过画笔在画布上随心所欲的绘制图案。如果浏览器不支持canvas会直接显示<canvas>标签中间自己设定的文字。当然<canvas>标签中间也可以是一张当不支持canvas时需要替换显示的图片。

 

2. 使用canvas的图像操作API绘制图像

绘制图像的关键API及参数说明:

分分快三计划 25

引用MDN上的一张图会比较清晰的看出每个参数的作用:

分分快三计划 26

drawImage就是把一个image对象或者canvas上(甚至是video对象上的的每一帧)指定位置和尺寸的图像绘制到当前的画布上。而在我们的需求中,是要把整个图像绘制到画布中。

分分快三计划 27

对应浏览器看到的效果:

分分快三计划 28

 

粒子动画

  粒子动画并不是粒子在做动画,而是通过getImageData()方法获得粒子的随机坐标和最终坐标后,通过fillRect()方法绘制的小方块在做运动。使用定时器,不断的绘制坐标变化的小方块,以此来产生运动的效果

【随机位置】

<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn1">开始显字</button>
<button id="btn2">重新混乱</button>
<script>
var drawing1 = document.getElementById('drawing1');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 200;
  var H = drawing1.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh   'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  cxt.clearRect(0,0,W,H);
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    //dots的索引
    var index = 0;
    for(var i = 0; i < W; i =n){
      for(var j = 0; j < H ;j =n){
        //data值中的红色值
        var k = 4*(i   j*W);
        //data值中的透明度
        if(imageData.data[k 3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
          dots[index  ] = {
            'index':index,
            'x':i,
            'y':j,
            'red':k,
            'randomX':Math.random()*W,
            'randomY':Math.random()*H,
          }
        }
      }
    }    
    //筛选粒子,仅保存dots.length/m个到newDots数组中
    var newDots = [];
    var len = Math.floor(dots.length/m);
    for(var i = 0; i < len; i  ){
      newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]);
    }
    return newDots;
  }
  //获得粒子数组
  var dataArr = setData(imageData,1,1);
  var oTimer1 = null;
  var oTimer2 = null;
  btn1.onclick = function(){
    clearTimeout(oTimer1);
    showData(10);
  }  
  btn2.onclick = function(){
    clearTimeout(oTimer2);
    showRandom(10);
  }    
  function showData(n){
    oTimer1 = setTimeout(function(){
      cxt.clearRect(0,0,W,H);
      for(var i = 0; i < dataArr.length; i  ){
        var temp = dataArr[i];
        var x0 = temp.randomX;
        var y0 = temp.randomY;
        var disX = temp.x - temp.randomX;
        var disY = temp.y - temp.randomY;
        cxt.fillRect(x0   disX/n,y0   disY/n,1,1);  
      }   
      showData(n-1);    
      if(n === 1){
        clearTimeout(oTimer1);
      }      
    },60);  
  } 
  function showRandom(n){
    oTimer2 = setTimeout(function fn(){
      cxt.clearRect(0,0,W,H);
      for(var i = 0; i < dataArr.length; i  ){
        var temp = dataArr[i];
        var x0 = temp.x;
        var y0 = temp.y;
        var disX = temp.randomX - temp.x;
        var disY = temp.randomY - temp.y;
        cxt.fillRect(x0   disX/n,y0   disY/n,1,1);             
      }     
      showRandom(n-1);    
      if(n === 1){
        clearTimeout(oTimer2);
      }      
    },60);  
  } 
}
</script>  

【飘入效果】 

   飘入效果与随机显字的原理相似,不再赘述

<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn1">左上角飘入</button>
<script>
var drawing1 = document.getElementById('drawing1');
if(drawing1.getContext){
  var cxt = drawing1.getContext('2d');
  var W = drawing1.width = 200;
  var H = drawing1.height = 200;
  var str = '小火柴';
  cxt.textBaseline = 'top';
  var sh = 60;
  cxt.font = sh   'px  宋体'
  var sw = cxt.measureText(str).width;
  if(sw > W){
      sw = W;
  }
  cxt.fillText(str,(W - sw)/2,(H - sh)/2,W);
  //获取imageData
  var imageData = cxt.getImageData(0,0,W,H); 
  cxt.clearRect(0,0,W,H);
  function setData(imageData,n,m){
    //从imageData对象中取得粒子,并存储到dots数组中
    var dots = [];
    //dots的索引
    var index = 0;
    for(var i = 0; i < W; i =n){
      for(var j = 0; j < H ;j =n){
        //data值中的红色值
        var k = 4*(i   j*W);
        //data值中的透明度
        if(imageData.data[k 3] > 0){
          //将透明度大于0的data中的红色值保存到dots数组中
          dots.push(k);
          dots[index  ] = {
            'index':index,
            'x':i,
            'y':j,
            'red':k,
            'randomX':Math.random()*W,
            'randomY':Math.random()*H,
          }
        }
      }
    }    
    //筛选粒子,仅保存dots.length/m个到newDots数组中
    var newDots = [];
    var len = Math.floor(dots.length/m);
    for(var i = 0; i < len; i  ){
      newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]);
    }
    return newDots;
  }
  //获得粒子数组
  var dataArr = setData(imageData,1,1);
  var oTimer1 = null;
  btn1.onclick = function(){
    clearTimeout(oTimer1);
    showData(10);
  }    
  function showData(n){
    oTimer1 = setTimeout(function(){
      cxt.clearRect(0,0,W,H);
      for(var i = 0; i < dataArr.length; i  ){
        var temp = dataArr[i];
        var x0 = 0;
        var y0 = 0;
        var disX = temp.x - 0;
        var disY = temp.y - 0;
        cxt.fillRect(x0   disX/n,y0   disY/n,1,1);  
      }   
      showData(n-1);    
      if(n === 1){
        clearTimeout(oTimer1);
      }      
    },60);  
  } 
}
</script>  

 

上面如果不理解, 对照代码运行一下试试理解吧:

二、制作粒子动画

制作粒子动画分两种:

一种是粒子漂浮类,这种比较简单,只需要随机的改变每个粒子的位置值,然后一直执行setInterval或者requestAnimationFrame重绘画布即可,具体的效果因人喜好而去设定,就不具体讲解了,做了个简单的粒子漂浮的例子。

另一种是粒子的轨迹动画,这个相对复杂一些。这里要介绍的是每个粒子按照指定的轨迹在指定的时间内做位移,最终汇聚成指定图案的动画效果(也就是文章一开始的动效),要做成这类动画效果需要解决两个问题:一个是动画轨迹,另外一个是每个粒子执行动画的时机。

     width,height为获取指定区域图像的信息

一、绘制粒子轮廓图

首先要在canvas画布上绘制一个由粒子组成的轮廓图,记录下每一个粒子的坐标,这样才能有后续的动画。

      第N个像素信息为: RGBA(data[(n-1)*4],data[(n-1)*4 1],data[(n-1)*4 2],data[(n-1)*4 3])

分分快三计划 29     分分快三计划 30

以上,谢谢阅读~

返回值说明:imageData为返回值,它是一个对象,包含三个属性

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<canvas id="myCanvas" width="600" height="400" style="background:#000">浏览器不支持canvas</canvas>
</body>
<script type="text/javascript">
    var canvas=document.getElementById("myCanvas");
    var ctx=canvas.getContext("2d");

    var image = new Image();
    image.src='/WebWorkspace/EchartDemostar.png';

    var pixels=[];  //存储像素数据
    var imageData;
    image.onload=function(){
        ctx.drawImage(image,200,100,200,200);
        imageData=ctx.getImageData(200,100,200,200);    //获取图表像素信息
        getPixels();    //获取所有像素
        drawPic();  //绘制图像

    };

    function getPixels(){
        var pos=0;
        var data=imageData.data;    //RGBA的一维数组数据
        //源图像的高度和宽度为200px
        for(var i=1;i<=200;i  ){
            for(var j=1;j<=200;j  ){
                pos=[(i-1)*200 (j-1)]*4; //取得像素位置
                if(data[pos]>=0){
                    var pixel={
                        x:200 j Math.random()*20, //重新设置每个像素的位置信息
                        y:100 i Math.random()*20, //重新设置每个像素的位置信息
                        fillStyle:'rgba(' data[pos] ',' (data[pos 1]) ',' (data[pos 2]) ',' (data[pos 3]) ')'
                    }
                    pixels.push(pixel);
                }

            }
        }
    }

    function drawPic(){
        var canvas=document.getElementById("myCanvas");
        var ctx=canvas.getContext("2d");
        ctx.clearRect(0,0,600,400);
        var len=pixels.length,curr_pixel=null;
        for(var i=0;i<len;i  ){
            curr_pixel=pixels[i];
            ctx.fillStyle=curr_pixel.fillStyle;
            ctx.fillRect(curr_pixel.x,curr_pixel.y,1,1);
        }
    }


</script>

</html>

  

var imageData=ctx.getImageData(x, y, width, height);

demo代码:

首先看一下源图和转换成粒子效果的对比图:

  第一个像素信息为:RGBA(data[0],data[1],data[2],data[3])

参数说明:x,y为画布上的x和y坐标

      .....

  其中,公式中的 i 表示行数,j 表示列数。200为图像的宽度。

      第二个像素信息为:RGBA(data[4],data[5],data[6],data[7])

左侧图片为源图,右侧图片为粒子效果图。该效果是在Canvas画布上制作的。将图片制作成粒子效果相对而言是比较简单的。重点了解两个知识点即可

1:图片是通过image对象形式绘制在画布上的,然后使用Canvas的getImageData接口,获取图像的像素信息。

本文由分分快三计划发布,转载请注明来源

关键词: 分分快三计划 JavaScript HTML5 canvas javascript总结