一步步實現nest粒子特效

本文首發於我的博客這是我的github,歡迎star。

  這篇博客是模仿nest.js實現一個demo,由簡單到複雜,來一步步的實現它。這裏是效果預覽我的github裏邊還有很多別的前端的demo,喜歡的話可以點個star,你的支持就是我的動力。

從一道面試題開始

實現一個半徑10px的小球在500px*500px的方塊中做直線運動,初始方向隨機,速度保持不變,碰撞到牆壁後反彈。

  看下效果,思路很簡單,將小球定位在方塊中,設置xy方向上的速度,每幀動畫給小球定位的值加上對應方向的速度值,在檢測到小球碰撞牆壁的時候,將對應方向的速度置爲反方向就可以了。這裏是實現的代碼,沒有用到canvas,但是思路一致。

嘗試實現

  畫出一個彈射的小球很簡單,那怎麼用多個小球實現nest.js這樣的效果呢。這樣的特效肯定不能用Dom直接做,太耗費性能,也做不出來,這時就顯露出canvas的強大之處了。
  同樣的,用canvas生成多個彈來彈去的小球。首先不要管鼠標如何吸附這些小圓點,只做小球之間的連線。在每次繪製小球之前,判斷一下它和之前的小球的距離是不是小於極限距離,小於就以它倆爲端點繪製一條線。代碼如下,思路都寫在註釋裏:

const theCanvas = document.getElementById('theCanvas'),
  ctx = theCanvas.getContext('2d'),
  mix = 6000;   //會產生連線的極限距離的平方

//將canvas鋪滿瀏覽器
let canvas_width = theCanvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
  canvas_height = theCanvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight,
  points = [];
theCanvas.style = "position: fixed; top: 0px; left: 0px;";

//繪製函數,用requestAnimationFrame調用實現動畫
function draw() {
  //清屏
  ctx.clearRect(0, 0, canvas_width, canvas_height);
  let i,pi,x_dist,y_dist;

  //遍歷點集合繪製線條
  points.forEach((p, index) => {
    p.x += p.xa,        //按指定速度移動
    p.y += p.ya,
    p.xa *= p.x > canvas_width || p.x < 0 ? -1 : 1,
    p.ya *= p.y > canvas_height || p.y < 0 ? -1 : 1,
    ctx.fillRect(p.x - 0.5, p.y - 0.5, 1, 1);       //繪製點,其實是小方塊

    //類似於握手問題,兩個點之間只繪製一次線
    for(i = index + 1; i < points.length; i++) {
      pi = points[i];
      x_dist = p.x - pi.x;
      y_dist = p.y - pi.y;
      dist = x_dist * x_dist + y_dist * y_dist;
      //判斷點之間的距離是否小於極限距離
      if(dist < mix) {
        ctx.beginPath();
        ctx.moveTo(p.x, p.y);
        ctx.lineTo(pi.x, pi.y);
        ctx.stroke();
      }
    }
  }),requestAnimationFrame(draw);
}

//隨機生成100個點
for(let i = 0; i < 100; i++ ) {

  let   x = Math.random() * canvas_width,   //初始座標
    y = Math.random() * canvas_height,
    xa = 2 * Math.random() - 1,         //x速度
    ya = 2 * Math.random() - 1;         //y速度

  points[i] = {x, y, xa, ya};
}

draw();

  看下效果,醜陋,和人家的不一樣,很生硬。因爲連線不是突然出現突然消失的,點和點之間的連線是漸漸的出現,然後漸漸消失的。給連線添加動態的屬性,用點和點的之間的距離來計算連線的粗細、透明度,在兩點距離比較遠的時候線會變淡,這樣看起來就舒服多了。

for(i = index + 1; i < points.length; i++) {
  pi = points[i];
  x_dist = p.x - pi.x;
  y_dist = p.y - pi.y;
  dist = x_dist * x_dist + y_dist * y_dist;
  //根據兩點距離得到一個參數w
  w = (mix - dist) / mix;
  //判斷點之間的距離是否小於極限距離
  if(dist < mix) {
    ctx.beginPath();
    //根據參數w設置連線寬度和透明度
    ctx.lineWidth = w / 2;
    ctx.strokeStyle = `rgba(110,110,110,${w + 0.2})`;
    ctx.moveTo(p.x, p.y);
    ctx.lineTo(pi.x, pi.y);
    ctx.stroke();
  }
}

添加鼠標事件

  先是加入對鼠標的響應。在鼠標進入瀏覽器時添加鼠標這個點,否則移除。

window.onmousemove = e => {
  e = e || window.event;
  current_point.x = e.clientX;
  current_point.y = e.clientY;
};
window.onmouseout = () => {
  current_point.x = null;
  current_point.y = null;
};
//將鼠標的點添加至點集合中
all_points = [...random_points,current_point];

  要實現一個鼠標吸附粒子的效果,思路就是粒子和鼠標的距離在一定範圍內時,給粒子添加一個向着鼠標的速度,結果就好像是粒子受到鼠標的吸附一樣。這是一段鼠標吸附效果的核心代碼:

//當兩點距離小於極限距離時產生連線,當第二個點是鼠標所產生點時,粒子如果在範圍內就會產生向鼠標點的速度,實現吸附效果
dist < pi.max && (pi === current_point && dist >= pi.max / 2 && (p.x -= 0.03 * x_dist, p.y -= 0.03 * y_dist));

  加入鼠標的點之後再做一些調整,得到最終的代碼

其他的粒子特效

  還可以利用canvasgetImageData屬性,將圖片粒子化,做成輪播圖,點擊這裏預覽,主要思路是用getImageData取到圖片像素點的信息,每隔一段取一個樣本,以這個樣本繪製粒子,繪製出類似於馬賽克一樣的圖片,然後給粒子加上運動的效果就可以了,這裏是具體的代碼實現

這篇博客到這就結束了,這是我的github,歡迎來訪,歡迎star。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章