用 canvas 做個好玩的網站背景


  不知不覺又好久沒更過博客了,老調新彈一下,之前做的一個小效果,覺得蠻有意思的,也有朋友問是怎么做的,就分享一下,寫個博文吧。

  先上demo吧:http://whxaxes.github.io/canvas-test/src//Funny-demo/netparticle/net_1.html  

  上面這個demo是最早寫的,后來做了點小修改后就用到了自己的網站上當個banner,有興趣的也可以看看效果:http://wanghx.cn/    ,至少同事說還是挺酷炫的。這種效果其實很早之前就有了的,我也是在一個網站上看到類似的效果,發現這個創意不錯,而且難度很小,就花了一個午休的時間寫了一下。

  接下來就分析一下怎么實現把。

  這種效果一眼看過去,就知道其實就是一堆粒子在進行無序的運動。然后當粒子與粒子之間的距離小於一定值后,就進行連線,並且根據距離的大小來對線條的粗細進行一些更改,就可以做出這種有點像蛛網的感覺了。原理很簡單,直接上代碼:

var dots = [];
  for (var i = 0; i < 200; i++) {
    var x = Math.random() * (canvas.width + 2*extendDis) - extendDis;
    var y = Math.random() * (canvas.height +  2*extendDis) - extendDis;
    var xa = (Math.random() * 2 - 1)/1.5;
    var ya = (Math.random() * 2 - 1)/1.5;

    dots.push({x, y, xa, ya})
  }

  首先,用一個數組,裝載兩百個分散在canvas各處的粒子對象,並且給每個對象一個隨機的運動趨勢。也就是xa和ya,用於表示垂直和水平的運動趨勢。其實就是一個用於每次循環的時候進行疊加的值。

  實例化好兩百個粒子對象后。就可以讓他們開始運動:

    dot.x += dot.xa;
    dot.y += dot.ya;

    // 遇到邊界將速度反向
    dot.xa *= (dot.x > (canvas.width + extendDis) || dot.x < -extendDis) ? -1 : 1;
    dot.ya *= (dot.y > (canvas.height + extendDis) || dot.y < -extendDis) ? -1 : 1;

    // 繪制點
    ctx.fillStyle = `rgba(${rgb},${rgb},${rgb},1`;
    ctx.fillRect(dot.x - 0.5, dot.y - 0.5, 1, 1);

  運動的邏輯也很簡單,每次給粒子更新新的狀態,其實就是根據此前初始化粒子的時候給予的xa和ya,進行一個累加,就可以形成運動的效果了。

  當然,粒子不能往一個方向無限的運動下去,所以我們還需要判斷粒子是否運動到邊界了,如果運動到了邊界,就把運動趨勢進行反轉。也就做出了一種粒子反彈的效果。上面的extendDis其實是我為了讓粒子反彈點在canvas外而定義的一個變量,用於控制粒子跑到離開canvas多遠后才進行反彈。

  當然,每次運動完都對粒子進行一個繪制。這一段代碼會放到一個叫move的function里。

  

  就上面的一些代碼,就完成了粒子的初始化,以及運動了。接下來就是畫線了。邏輯也很簡單,就是遍歷,逐個粒子計算距離,當兩個比較的粒子之間的距離小於某個值,就進行畫線。代碼如下:

  /**
   * 逐個對比連線
   * @param ndots
   */
  function bubDrawLine(ndots){
    var ndot;

    dots.forEach(function (dot) {

      move(dot);

      // 循環比對粒子間的距離
      for (var i = 0; i < ndots.length; i++) {
        ndot = ndots[i];

        if (dot === ndot || ndot.x === null || ndot.y === null) continue;

        var xc = dot.x - ndot.x;
        var yc = dot.y - ndot.y;

        // 如果x軸距離或y軸距離大於max,則不計算粒子距離
        if(xc > ndot.max || yc > lineDis) continue;

        // 兩個粒子之間的距離
        var dis = xc * xc + yc * yc;

        // 如果粒子距離超過max,則不做處理
        if( dis > lineDis ) continue;

        // 距離比
        var ratio;

        // 如果是鼠標,則讓粒子向鼠標的位置移動
        if (ndot === warea && dis < 20000) {
          dot.x -= xc * 0.01;
          dot.y -= yc * 0.01;
        }

        // 計算距離比
        ratio = (lineDis - dis) / lineDis;

        // 粒子間連線
        ctx.beginPath();
        ctx.lineWidth = ratio / 2;
        ctx.strokeStyle = `rgba(${rgb},${rgb},${rgb},${ratio + 0.2}`;
        ctx.moveTo(dot.x, dot.y);
        ctx.lineTo(ndot.x, ndot.y);
        ctx.stroke();
      }

      // 將已經計算過的粒子從數組中刪除
      ndots.splice(ndots.indexOf(dot), 1);
    });
  }

  邏輯也比較簡單,就是遍歷數組,把遍歷到的粒子跟其他粒子進行逐個比對。當距離小於上面的lineDis的時候,就進行連線。為了減少計算量,每次計算過的粒子將會從用於計算的ndots數組中刪除,避免重復計算。同時如果兩個粒子的垂直距離和水平距離大於lineDis,那也就沒必要再算兩個粒子的距離了,直接不做處理,從而減少計算量。

  其實這個計算用的還是所謂的笨方法,我此前有在想有什么更好的計算方法能更好的優化計算效率呢。然后想了一個方法並且進行了一個測試,就是先對粒子根據x軸進行快速排序,然后按順序進行比較,當比較到的粒子的水平距離大於lineDis的時候,就不用再比下去了。因為后面的都肯定會比當前粒子要更遠,想着就按照這樣會減少計算量應該會提升效率。但是我對兩個不同的計算方法都進行了耗時比較,結果還是原來的笨方法的性能更優。因為這個新方法每次都要重新排序,這個計算量也是蠻大的。然后就暫時沒想到其他了,如果讀者有更好的idea不妨分享一下。

  

  同事有問我那個鼠標划過,粒子會聚起來的效果很神奇,怎么做的,其實這個效果比想象中簡單很多,而且在上面的代碼里我也給出來了。再給出一段保存鼠標位置的代碼,很簡單,就是鼠標移動的時候保存鼠標位置。

  // 鼠標活動時,獲取鼠標坐標
  var warea = {x: null, y: null};
  var animateHeader = document.getElementById("animateHeader");

  animateHeader.onmousemove = function (e) {
    e = e || window.event;

    warea.x = e.clientX + 10;
    warea.y = e.clientY;
  };

  保存了鼠標位置后,在每次動畫循環的時候,把鼠標位置也當成一個粒子對象塞進數組進行比較:

  // 每一幀循環的邏輯
  function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    bubDrawLine([warea].concat(dots));

    RAF(animate);
  }

  而粒子往鼠標方向運動的代碼,其實就這么一小截:

  // 如果是鼠標,則讓粒子向鼠標的位置移動
  if (ndot === warea && dis < 20000) {
    dot.x -= xc * 0.01;
    dot.y -= yc * 0.01;
  }

  計算鼠標與粒子的距離,當鼠標與粒子之間的距離小於一定的時候,把粒子的位置更新為 “當前位置 - 鼠標粒子距離 * 0.01”即可。然后就會形成粒子往鼠標位置移動的效果了。

  整個效果就這樣完成了,很簡單,也很有意思,有興趣的可以去研究一下發掘一些更好玩的效果。

  貼上這個demo的github地址:https://github.com/whxaxes/canvas-test/tree/gh-pages/src//Funny-demo/netparticle  

  這個demo是很早之前寫的,跟上面貼出來的代碼會有點出入,但是原理是一樣的。懂了原理,就可以自己去實現一個了。

  

  如果覺得demo不錯,就在github給個star唄,當然也歡迎fork

 


免責聲明!

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



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