canvas粒子系统的构建

Posted 小火柴的蓝色理想

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了canvas粒子系统的构建相关的知识,希望对你有一定的参考价值。

前面的话

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

 

效果演示

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

 

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个属性:width\\height\\data

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

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

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

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

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

  数组中每个元素的值是在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对象放回画布上,该方法共有以下参数

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

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

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

 

粒子写入

  粒子,指图像数据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个

 

粒子筛选

  当粒子完全写入时,与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以上是关于canvas粒子系统的构建的主要内容,如果未能解决你的问题,请参考以下文章

canvas——粒子系统

canvas动画——粒子系统

canvas绘图基础及基于粒子系统的雪花飘落

Javascript - 交互式粒子徽标不起作用

重磅:canvas之粒子效果

使用 CSS 构建强大且酷炫的粒子动画