canvas粒子系統的構建

前面的話

  本文將從最基本的imageData對象的理論知識說開去,詳細介紹canvas粒子系統的構建html

 

效果演示

  下面是實例效果演示,博文結尾有所有源碼canvas

 

imageData

  關於圖像數據imageData共有3個方法,包括getImageData()、putImageData()、createImageData()跨域

【getImageData()】數組

  2D上下文能夠經過getImageData()取得原始圖像數據。這個方法接收4個參數:畫面區域的x和y座標以及該區域的像素寬度和高度dom

  例如,要取得左上角座標爲(10,5)、大小爲50*50像素的區域的圖像數據,可使用如下代碼:函數

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

  返回的對象是ImageData的實例,每一個ImageData對象有3個屬性:width\height\data動畫

  一、width:表示imageData對角的寬度spa

  二、height:表示imageData對象的高度指針

  三、data是一個數組,保存着圖像中每個像素的數據。在data數組中,每個像素用4個元素來保存,分別表示red、green、blue、透明度code

  [注意]圖像中有多少像素,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+=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>  

  點擊下面的不一樣按鈕,能夠篩選出對應數量的隨機的粒子

 

像素顯字

  下面來使用粒子篩選來實現一個像素顯字的效果。像素顯字即從不清晰的效果逐步過渡到徹底顯示

【按序像素顯字】

  按序像素顯字的實現原理很是簡單,好比,共有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> 

 

粒子動畫

  粒子動畫並非粒子在作動畫,而是經過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>  

 

鼠標交互

  通常地,粒子的鼠標交互都與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中的文字,出現效果

 

綜合實例

  下面將上面的效果製做爲一個可編輯的綜合實例

<!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">
  <span>粒子設置:</span>
  <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">
  <span>粒子效果:</span>
  <button id="btn1">按序顯字</button>
  <button id="btn2">隨機顯字</button>  
  <button id="btn3">混亂聚合</button>
  <button id="btn4">從新混亂</button>
</div>
<div>
  <span>鼠標效果:</span>
  <span>一、鼠標移到文字上時,文字顏色變紅;</span>
  <span>二、鼠標在文字上點擊時,粒子遠離鼠標指針</span>
</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>
相關文章
相關標籤/搜索