1. 程式人生 > 實用技巧 >canvas把資料轉為粒子特效

canvas把資料轉為粒子特效

前言

  之前經常在一些網站看到一些特別炫酷的特效,例如文字呈粒子狀的特效,或圖片的蒙太奇效果,剛巧找了相關的canvas動畫研究了一些,因此在這裡做一個簡單的總結。

   思路   一個畫素點是由四個值組成的RGBA,第一個畫素點RGBA[data[0],data[1],data[2],data[3]];第二個畫素點RGBA[data[4],data[5],data[6],data[7]];第N個畫素點RGBA[data[(n-1)*4],data[(n-1)*4]+1,data[(n-1)*4]+2,data[(n-1)*4]+3];整體畫素是一個區域200*200空間,上面的分析只適合單獨一行進行計算的時候,內部定位,每一個畫素一行一列的時候,取得第i行第j列的資訊constpos=[(i-1)*200+(j-1)]*4;

案例一:

  圖片的蒙太奇效果

  程式碼

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>H5蒙太奇效果</title>
</head>
<body>
  <canvas id="myCanvas" width="600" height
="400" style="background-color: #200;"></canvas> </body> <script> const canvas = document.getElementById("myCanvas"); const ctx = canvas.getContext("2d"); const img = new Image(); img.src = "https://tse4-mm.cn.bing.net/th/id/OIP.WpP_Wt3bD5BVaKuljRcEywHaJh?w=206&h=265&c=7&o=5&dpr=2.5&pid=1.7
"; img.crossOrigin = ''; // 計算所有的畫素點 let pixels = []; let imageData; img.onload = ()=>{ ctx.drawImage(img,200,100,200,200); imageData = ctx.getImageData(200,100,200,200); getPixels(); drawPic(); // console.log(imageData); console.log("新的畫素點",pixels); }; function getPixels() { const data = imageData.data; for(let i=0;i<=200;i++){ let pos = 0; for(let j=0;j<=200;j++){ pos = [(i-1)*200+(j-1)]*4; if(data[pos] >= 0) { const 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() { ctx.clearRect(0,0,600,400); let curr_pixel = null; for(const _piexel of pixels) { const {x,y,fillStyle} = _piexel; ctx.fillStyle = fillStyle; ctx.fillRect(x,y,1,1); } } </script> </html>

案例二:

  倒計時

    這個就是不斷重繪canvas,產生炫酷的粒子動畫效果。

  程式碼

(function (window) {
  window.requestAnimationFrame =
    window.requestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.msRequestAnimationFrame;

  const PARTICLE_NUM = 2000;//顆粒點
  const RADIUS = Math.PI * 2;//半徑
  const CANVASWIDTH = 700;//
  const CANVASHEIGHT = 150;//
  const CANVASID = "canvas";//ID

  let canvas,
    ctx,
    particles = [],//顆粒點陣列
    quiver = true,//是否需要抖動
    text = formatTime(new Date());//文字--當前時間
    textSize = 150;//字型大小

  function draw() {
    ctx.clearRect(0, 0, CANVASWIDTH, CANVASHEIGHT);
    ctx.fillStyle = "rgb(255, 255, 255)";
    ctx.textBaseline = "middle";
    ctx.fontWeight = "bold";
    ctx.font = textSize + "px 'SimHei', 'Avenir', 'Helvetica Neue', 'Arial', 'sans-serif'";
    ctx.fillText(text,(CANVASWIDTH - ctx.measureText(text).width) * 0.5,CANVASHEIGHT * 0.5);

    let imgData = ctx.getImageData(0, 0, CANVASWIDTH, CANVASHEIGHT);

    ctx.clearRect(0, 0, CANVASWIDTH, CANVASHEIGHT);

    for (let i = 0, l = particles.length; i < l; i++) {
      let p = particles[i];
      p.inText = false;
    }
    particleText(imgData);

    window.requestAnimationFrame(draw);
  }

  function particleText(imgData) {
    // 點座標獲取
    var pxls = [];
    for (var w = CANVASWIDTH; w > 0; w -= 3) {
      for (var h = 0; h < CANVASHEIGHT; h += 3) {
        var index = (w + h * CANVASWIDTH) * 4;
        if (imgData.data[index] > 1) {
          pxls.push([w, h]);
        }
      }
    }

    var count = pxls.length;
    var j = parseInt((particles.length - pxls.length) / 2, 10);
    j = j < 0 ? 0 : j;

    for (var i = 0; i < pxls.length && j < particles.length; i++, j++) {
      try {
        var p = particles[j],
          X,
          Y;

        if (quiver) {
          X = pxls[i - 1][0] - (p.px + Math.random() * 10);
          Y = pxls[i - 1][1] - (p.py + Math.random() * 10);
        } else {
          X = pxls[i - 1][0] - p.px;
          Y = pxls[i - 1][1] - p.py;
        }
        var T = Math.sqrt(X * X + Y * Y);
        var A = Math.atan2(Y, X);
        var C = Math.cos(A);
        var S = Math.sin(A);
        p.x = p.px + C * T * p.delta;
        p.y = p.py + S * T * p.delta;
        p.px = p.x;
        p.py = p.y;
        p.inText = true;
        p.fadeIn();
        p.draw(ctx);
      } catch (e) {}
    }
    for (var i = 0; i < particles.length; i++) {
      var p = particles[i];
      if (!p.inText) {
        p.fadeOut();

        var X = p.mx - p.px;
        var Y = p.my - p.py;
        var T = Math.sqrt(X * X + Y * Y);
        var A = Math.atan2(Y, X);
        var C = Math.cos(A);
        var S = Math.sin(A);

        p.x = p.px + (C * T * p.delta) / 2;
        p.y = p.py + (S * T * p.delta) / 2;
        p.px = p.x;
        p.py = p.y;

        p.draw(ctx);
      }
    }
    console.log(pxls);
  }

  function setDimensions() {
    canvas.width = CANVASWIDTH;
    canvas.height = CANVASHEIGHT;
    canvas.style.position = "absolute";
    canvas.style.left = "0px";
    canvas.style.top = "0px";
    canvas.style.bottom = "0px";
    canvas.style.right = "0px";
    canvas.style.marginTop = window.innerHeight * 0.15 + "px";
  }

  function event() {
    setInterval(() => {
      text = formatTime(new Date());
    }, 1000);
  }

  function formatTime(date) {
    var h = date.getHours(),
      m = date.getMinutes(),
      s = date.getSeconds(),
      m = m < 10 ? "0" + m : m;
    s = s < 10 ? "0" + s : s;
    return h + ":" + m + ":" + s;
  }

  function init() {
    canvas = document.getElementById(CANVASID);
    if (canvas === null || !canvas.getContext) {
      return;
    }
    ctx = canvas.getContext("2d");
    setDimensions();
    event();

    for (var i = 0; i < PARTICLE_NUM; i++) {
      particles[i] = new Particle(canvas);
    }

    draw();
  }

  class Particle {
    constructor(canvas) {
      let spread = canvas.height;
      let size = Math.random() * 1.2;
      // 速度
      this.delta = 0.06;
      // 現在的位置
      this.x = 0;
      this.y = 0;
      // 上次的位置
      this.px = Math.random() * canvas.width;
      this.py = canvas.height * 0.5 + (Math.random() - 0.5) * spread;
      // 記錄點最初的位置
      this.mx = this.px;
      this.my = this.py;
      // 點的大小
      this.size = size;
      // this.origSize = size
      // 是否用來顯示字
      this.inText = false;
      // 透明度相關
      this.opacity = 0;
      this.fadeInRate = 0.005;
      this.fadeOutRate = 0.03;
      this.opacityTresh = 0.98;
      this.fadingOut = true;
      this.fadingIn = true;
    }
    fadeIn() {
      this.fadingIn = this.opacity > this.opacityTresh ? false : true;
      if (this.fadingIn) {
        this.opacity += this.fadeInRate;
      } else {
        this.opacity = 1;
      }
    }
    fadeOut() {
      this.fadingOut = this.opacity < 0 ? false : true;
      if (this.fadingOut) {
        this.opacity -= this.fadeOutRate;
        if (this.opacity < 0) {
          this.opacity = 0;
        }
      } else {
        this.opacity = 0;
      }
    }
    draw(ctx) {
      ctx.fillStyle = "rgba(226,225,142, " + this.opacity + ")";
      ctx.beginPath();
      ctx.arc(this.x, this.y, this.size, 0, RADIUS, true);
      ctx.closePath();
      ctx.fill();
    }
  }

  init();
})(window);

  

  html

<!doctype html>
<html lang="">

<head>
  <meta charset="utf-8">
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
  <title>倒計時</title>
  <style>
    html,
    body {
      padding: 0px;
      margin: 0px;
      width: 100%;
      height: 100%;
      position: fixed;
    }

    body {
      display: -webkit-box;
      display: -webkit-flex;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-pack: center;
      -webkit-justify-content: center;
      -ms-flex-pack: center;
      justify-content: center;
      -webkit-box-align: center;
      -webkit-align-items: center;
      -ms-flex-align: center;
      align-items: center;
      -webkit-filter: contrast(120%);
      filter: contrast(120%);
      background-color: black;
    }

    .container {
      width: 100%;
      height: 100%;
      background-image: radial-gradient(1600px at 70% 120%, rgba(33, 39, 80, 1) 10%, #020409 100%);

    }

    .content {
      width: inherit;
      height: inherit;
    }

    #canvas {
      margin: 200px auto;
    }
  </style>
</head>

<body>
  <div class="container">
    <div class="content">
      <canvas id="canvas"></canvas>
    </div>
  </div>
  <script src="scripts/main.js"></script>
</body>

</html>

總結

  粒子化最關鍵的在於getImageData(),獲取畫素點,然後不斷清除之前的樣式再進行重繪,像上面的案例ctx.clearRect(0,0,CANVASWIDTH,CANVASHEIGHT); 再ctx.fillText(text,(CANVASWIDTH-ctx.measureText(text).width)*0.5,CANVASHEIGHT*0.5); ctx.getImageData(0,0,CANVASWIDTH,CANVASHEIGHT);