js canvas 粒子動畫 電子表


 前言

從我接觸canvas的第一天就覺得canvas很有趣,想搞點事情,這幾天終於忍不住了,於是他來了。 

先看效果

                     

這里我做了四個大家有興趣可以看完文章,做一個自己喜歡的動畫。

思路

開始做之前,我們先分析一下這種粒子動畫實現的原理,繪制的內容是由許多個帶有顏色像素點構成,每個像素點在畫布上都有自己的坐標。首先獲取到要繪制的內容的像素點信息的數組(目標數組)例如 

[
    {x:10, y:20, color: 'rgba(255, 122, 122)'},
    {x:11, y:20, color: 'rgba(255, 122, 122)'},
    {x:12, y:20, color: 'rgba(255, 122, 122)'},
]

 

 然后我們就可以讓這些像素點從某些特定的位置,以某種特定的方式,移動到目標位置,動畫就完成了。

實現

1.獲取目標數組

我們先說一下 canvas 的 getImageData() ,該方法返回 ImageData 對象,該對象拷貝了畫布指定矩形的像素數據。

對於 ImageData 對象中的每個像素,都存在着四方面的信息,即 RGBA 值:

  • R - 紅色 (0-255)
  • G - 綠色 (0-255)
  • B - 藍色 (0-255)
  • A - alpha 通道 (0-255; 0 是透明的,255 是完全可見的)

真實樣子是這個樣子的

 

 

0,1,2,3 4,5,6,7 8,9,10,11
12,13,14,15 16,17,18,19 20,21,22,23

 

 

 

 

每四個值為一組,用來表示一個像素點的信息,每一個單元格代表一個像素。

 

先在一個canvas中繪制想要的內容,通過getImageData()獲得像素信息,我們發現ImageData 對象的信息和我們想象中的目標數組不大一樣,我們要將ImageData對象處理一下,我們將其每四個划分為一組,重新定義索引,例如我們在一個12px寬的畫布中,經過分析不難發現坐標與索引之間的關系,分兩種情況 n<12(畫布的寬度) 時坐標為((n+1)%12, n+1),n>12時坐標為((n+1)%12, parseInt((n+1)/ 12))

0(0,0) 1(0,1) .. n((n+1)%12, n+1) 11(0,11)
.. ..       ..          ..           ..            
     

n((n+1)%12, parseInt((n+1)/ 12))

 

 

 

 

 

 

 

 

到這里功能是實現了,但是如果操作的內容很大,像素點很多,后期操作的像素點越多性能就越差,有沒有什么辦法可以稀釋一下這些像素呢,當然有!我們可以隔一個像素取一個像素,這樣像素點瞬間就減少了一倍,同理我們隔兩個隔三個隔n個,這樣我們就可以定義一個參數用來控制像素的稀釋度

下面的事情就簡單了,用代碼實來現這一步

/*
* @ ImageDataFormat
* @ param { pixels 需要格式化的ImageData對象, n 稀釋度 }
* @ return { Array }
*/ 

 function ImageDataFormat(pixels, n){
    n = n*4
var arr = [], //目標數組     temPixel = {}, //目標數組中存放像素信息的對象     x = 0, //像素的x坐標     y = 0 //像素的y坐標 for (var i=0;i<pixels.data.length;i+=n){       //過濾純色背景提高性能,如背景色不可去掉可省略判斷 if(pixels.data[i] !== 0 || pixels.data[i+1] !== 0 || pixels.data[i+2] !== 0 ){ var index = (i+1) / 4 //每四個划分為一組,重新定義索引 if(index > timeDom.width){ x = index % timeDom.width y = parseInt(index / timeDom.width) }else{ x = index y = 0 } temPixel = { R: pixels.data[i], G: pixels.data[i+1], B: pixels.data[i+2], A: pixels.data[i+3], I:i, X:x, Y:y } arr.push(temPixel) } }
    return arr }

2.將目標數組繪制到畫布上

 

2.1在畫布的指定位置畫一個圓(一個像素點)

  /**
 * @ drawArc
 * @ param{ ctx 畫布,,x x坐標,y y坐標,color 顏色}
 */
function drawArc(ctx, x, y, color){
  x = x 
  y = y 
  ctx.beginPath();
  ctx.fillStyle = color
  ctx.strokeStyle = color
  ctx.arc(x,y,0.5,0,2*Math.PI);
  ctx.closePath()
  ctx.fill()
}

 

2.1將點連成線,線構成面

  /**
 * 畫路徑
 * @param { points 格式化好的目標數組, crx 畫布}
 */
function draw_path(points, ctx){

  for(var i=0;i < points.length-1;i++){

    var color = 'rgba(' + points[i].R + ',' + points[i].G + ',' + points[i].B + ')', x, y drawArc(ctx,points[i].X,points[i].Y, color) } }

到此我們就畫出了動畫的其中一幀,下面我們就要讓這一幀動起來

2.2動起來

 

我們的動畫進行其實很簡單

 

1.畫第一幀

 

2.清空畫布

 

3.畫下一幀

 

4.在清空

 

....

但是想讓這個動畫流暢的進行起來我們還要在了解一下tween(緩動動畫), window.requestAnimationFrame()

tween 我們值列舉一種其他 形式感興趣的可以自己查一下

/*
* @ 參數描述
* @ t 動畫執行到當前幀所經過的時間
* @ b 起始值
* @ c 總位移值
* @ d 持續時間
*/
function easeInOutExpon(t,b,c,d){
    if (t==0) return b;
    if (t==d) return b+c;
    if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
    return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
}

 

 

 window.requestAnimationFrame()

 

 

准備工作做好了可以開工了

我么只需要將前面的函數稍微改動一下他就動起來了

  function ShowTimeInit(pixels, n){
        n = 4*n
      var arr = [], temPixel = {}, x = 0, y = 0
      for (var i=0;i<pixels.data.length;i+=n){
        if(pixels.data[i] !== 0 ||  pixels.data[i+1] !== 0 || pixels.data[i+2] !== 0 ){
          var index = parseInt ((i+1) / 4)
          if(index > timeDom.width){
            x = index % timeDom.width
            y = parseInt(index / timeDom.width) 
          }else{
            x = index
            y = 0
          }
          temPixel = {
            R: pixels.data[i], 
            G: pixels.data[i+1], 
            B: pixels.data[i+2], 
            A: pixels.data[i+3], 
            I:i,
            X:x,
            Y:y
          }
          
          arr.push(temPixel)
        }

      }
      var step = requestAnimationFrame(function(){draw_path(arr, ShowTime, step)})
      
  }
  /**
 * 畫路徑
 * @param path 路徑
 */
function draw_path(points, ctx, step){
    ShowTime.clearRect(0,0,ShowTimeDom.width,ShowTimeDom.height);
    var pointX, pointY, randomX, randomY
    for(var i=0;i < points.length-1;i++){
        switch (mode){
            case 'left':
                pointX = randomNum(0,0)
                pointY = randomNum(0,100)
                randomX = 0
                randomY = Math.random() + Math.random()*3000
                break;
            case 'center':
                pointX = 80
                pointY = 50
                randomX = Math.random() + Math.random()*3000
                randomY = Math.random() + Math.random()*3000
                break;
            case 'random':
                pointX = 0
                pointY = 0
                randomX = Math.random() + Math.random()*3000
                randomY = Math.random() + Math.random()*3000
                break;        
            case 'flow':
                pointX = 0
                pointY = 0
                randomX = i
                randomY = i
                break;
        }

      var color = 'rgba(' + points[i].R + ',' +  points[i].G + ','  + points[i].B + ')', x, y
       x = easeInOutExpon(nowDuration + randomX, pointX, points[i].X-pointX, duration)
       y = easeInOutExpon(nowDuration + randomY, pointY, points[i].Y-pointY, duration)
      drawArc(ctx,x, y, color)
      
    }
    nowDuration += 1000/60
        if(duration <= nowDuration){
        window.cancelAnimationFrame(step);
    }else{
        requestAnimationFrame(function(){draw_path(points, ctx, step)})
    }
    
}

 

附上完整代碼

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
  
  </style>
  <title>電子時鍾</title>
</head>
<body>
  <canvas id="HidenTime" width="300" height="100" style="display: none"> </canvas>
  <canvas id="ShowTime" width="300" height="100"> </canvas>
</body>
<script>
  function GetTime(){
    this._Hours = ''
    this._Minutes = ''    
    this._Seconds = ''
  }
  GetTime.prototype = {
    constructor: GetTime,
    get Hours(){
      this._Hours = new Date().getHours()
      if(this._Hours > 9){
        return this._Hours
      }else{
        return "0" + this._Hours
      }
    },
    get Minutes(){
      this._Minutes = new Date().getMinutes()
      if(this._Minutes > 9){
        return this._Minutes
      }else{
        return "0" + this._Minutes
      }
    },
    get Seconds(){
      this._Seconds = new Date().getSeconds()
      if(this._Seconds > 9){
        return this._Seconds
      }else{
        return "0" + this._Seconds
      }
    },
    formTime:function(){
        return this.Hours + ':' + this.Minutes + ':' + this.Seconds
    }
  }
  var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
                            window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  var cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
  var duration = 3000, nowDuration = 0
  var timeDom = document.getElementById("HidenTime")
  var time = timeDom.getContext('2d')
  var ShowTimeDom = document.getElementById("ShowTime")
  var ShowTime = ShowTimeDom.getContext('2d')
  time.clearRect(0,0,timeDom.width,timeDom.height);
  var nowTime = new GetTime()
  var showTime = nowTime.formTime()
  var modes = ['left', 'random', 'center', 'flow']
  var mode = modes[0]
  time.font="50px Verdana";
  // 創建漸變
  var gradient=time.createLinearGradient(0,0,timeDom.width,0);
  gradient.addColorStop("0","magenta");
  gradient.addColorStop("0.5","blue");
  gradient.addColorStop("1.0","red");
  // 用漸變填色
  time.fillStyle=gradient;
  time.fillText(showTime,10,60);
  var pixels = time.getImageData(0,0,300,100)
  ShowTimeInit(pixels, 2)
  setInterval(function(){
    
    mode = modes[randomNum(0,3)]
    //mode = modes[3]
    time.clearRect(0,0,timeDom.width,timeDom.height);
    nowDuration = 0
    showTime = nowTime.formTime()
    time.fillText(showTime,10,60);
    pixels = time.getImageData(0,0,300,100)
    ShowTimeInit(pixels, 2)
  }, 5000)
  function ShowTimeInit(pixels, n){
        n = 4*n
      var arr = [], temPixel = {}, x = 0, y = 0
      for (var i=0;i<pixels.data.length;i+=n){
        if(pixels.data[i] !== 0 ||  pixels.data[i+1] !== 0 || pixels.data[i+2] !== 0 ){
          var index = parseInt ((i+1) / 4)
          if(index > timeDom.width){
            x = index % timeDom.width
            y = parseInt(index / timeDom.width) 
          }else{
            x = index
            y = 0
          }
          temPixel = {
            R: pixels.data[i], 
            G: pixels.data[i+1], 
            B: pixels.data[i+2], 
            A: pixels.data[i+3], 
            I:i,
            X:x,
            Y:y
          }
          
          arr.push(temPixel)
        }

      }
      var step = requestAnimationFrame(function(){draw_path(arr, ShowTime, step)})
      
  }
  /**
 * 畫路徑
 * @param path 路徑
 */
function draw_path(points, ctx, step){
    ShowTime.clearRect(0,0,ShowTimeDom.width,ShowTimeDom.height);
    var pointX, pointY, randomX, randomY
    for(var i=0;i < points.length-1;i++){
        switch (mode){
            case 'left':
                pointX = randomNum(0,0)
                pointY = randomNum(0,100)
                randomX = 0
                randomY = Math.random() + Math.random()*3000
                break;
            case 'center':
                pointX = 80
                pointY = 50
                randomX = Math.random() + Math.random()*3000
                randomY = Math.random() + Math.random()*3000
                break;
            case 'random':
                pointX = 0
                pointY = 0
                randomX = Math.random() + Math.random()*3000
                randomY = Math.random() + Math.random()*3000
                break;        
            case 'flow':
                pointX = 0
                pointY = 0
                randomX = i
                randomY = i
                break;
        }

      var color = 'rgba(' + points[i].R + ',' +  points[i].G + ','  + points[i].B + ')', x, y
       x = easeInOutExpon(nowDuration + randomX, pointX, points[i].X-pointX, duration)
       y = easeInOutExpon(nowDuration + randomY, pointY, points[i].Y-pointY, duration)
      drawArc(ctx,x, y, color)
      
    }
    nowDuration += 1000/60
        if(duration <= nowDuration){
        window.cancelAnimationFrame(step);
    }else{
        requestAnimationFrame(function(){draw_path(points, ctx, step)})
    }
    
}
  /**
 * 畫圓
 */
function drawArc(ctx, x, y, color){
  x = x 
  y = y 
  ctx.beginPath();
  ctx.fillStyle = color
  ctx.strokeStyle = color
  ctx.arc(x,y,0.5,0,2*Math.PI);
  ctx.closePath()
  ctx.fill()
}






/*
* 參數描述
* t 動畫執行到當前幀所經過的時間
* b 起始值
* c 總位移值
* d 持續時間
*/
function easeInOutExpon(t,b,c,d){
    if (t==0) return b;
    if (t==d) return b+c;
    if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
    return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
}

//生成從minNum到maxNum的隨機數
function randomNum(minNum, maxNum) {
  switch (arguments.length) {
    case 1:
      return parseInt(Math.random() * minNum + 1, 10);
      break;
    case 2:
      return parseInt(Math.random() * ( maxNum - minNum + 1 ) + minNum, 10);
      break;
    default:
      return 0;
      break;
  }
}
</script>
</html>
View Code

 

 

 總結

當一個新想法出現時,先去github和博客上找一找,看看有沒有大佬做過,大佬們的的思路是什么,有什么自己沒想到的細節,感覺差不多了在動手去做。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM