天天看點

【HTML】109-canvas 中普通動效與粒子動效的實作

canvas 用于在網頁上繪制圖像、動畫,可以将其了解為畫布,在這個畫布上建構想要的效果。

canvas 可以繪制動态效果,除了常用的規則動畫之外,還可以采用粒子的概念來實作較複雜的動效,本文分别采用普通動效與粒子特效實作了一個簡單的時鐘。

普通時鐘

普通動效即利用 canvas 的 api,實作有規則的圖案、動畫。

效果

【HTML】109-canvas 中普通動效與粒子動效的實作

該效果實作比較簡單,主要分析一下刻度與指針角度偏移的實作。

繪制刻度

此例為小時刻度的繪制:表盤上共有 12 個小時,Math.PI 為 180°,每小時占據 30°。

.save()表示儲存 canvas 目前環境的狀态,在此基礎上進行繪制。繪制完成之後,傳回之前儲存過的路徑狀态和屬性。

分鐘刻度同理,改變角度與樣式即可。

// 小時時間刻度

offscreenCanvasCtx.save();

for (var i = 0; i < 12; i++) {

  offscreenCanvasCtx.beginPath();

  // 刻度顔色

  offscreenCanvasCtx.strokeStyle = "#fff";

  // 刻度寬度

  offscreenCanvasCtx.lineWidth = 3;

  // 每小時占據30°

  offscreenCanvasCtx.rotate(Math.PI / 6);

  // 開始繪制的位置

  offscreenCanvasCtx.lineTo(140, 0);

  // 結束繪制的位置;

  offscreenCanvasCtx.lineTo(120, 0);

  // 繪制路徑

  offscreenCanvasCtx.stroke();

}

offscreenCanvasCtx.restore();

指針指向

以秒針為例:擷取目前時間的秒數,并計算對應的偏移角度

  var now = new Date(),

    sec = now.getSeconds(),

    min = now.getMinutes(),

    hr = now.getHours();

  hr = hr 12 ? hr - 12 : hr;

  //秒針

  offscreenCanvasCtx.save();

  offscreenCanvasCtx.rotate(sec * (Math.PI / 30));

  ......

  offscreenCanvasCtx.stroke();

粒子動效

canvas 可以用來繪制複雜,不規則的動畫。粒子特效可以用來實作複雜、随機的動态效果。

粒子,指圖像資料imageData中的每一個像素點,擷取到每個像素點之後,添加屬性或事件對區域内的粒子進行互動,達到動态效果。

效果

【HTML】109-canvas 中普通動效與粒子動效的實作

粒子擷取

以下圖的圖檔轉化為例,該效果是先在 canvas 上渲染圖檔,然後擷取文字所在區域的每個像素點。

let image = new Image();

image.src = "../image/logo.png";

let pixels = []; //存儲像素資料

let imageData;

image.width = 300;

image.height = 300;

// 渲染圖檔,并擷取該區域内像素資訊

image.onload = function() {

  ctx.drawImage(

    image,

    (canvas.width - image.width) / 2,

    (canvas.height - image.height) / 2,

    image.width,

    image.height

  );

  imageData = ctx.getImageData(

    (canvas.width - image.width) / 2,

    (canvas.height - image.height) / 2,

    image.width,

    image.height

  ); //擷取圖表像素資訊

  //繪制圖像

};

像素資訊

圖檔的大小為 300*300,共有 90000 個像素,每個像素占 4 位,存放 rgba 資料。

【HTML】109-canvas 中普通動效與粒子動效的實作

粒子繪制

function getPixels() {

  var pos = 0;

  var data = imageData.data; //RGBA的一維數組資料

  //源圖像的高度和寬度為300px

  for (var i = 1; i <= image.width; i++) {

    for (var j = 1; j <= image.height; j++) {

      pos = [(i - 1) * image.width + (j - 1)] * 4; //取得像素位置

      if (data[pos] >= 0) {

        var pixel = {

          x: (canvas.width - image.width) / 2 + j + Math.random() * 20, //重新設定每個像素的位置資訊

          y: (canvas.height - image.height) / 2 + i + Math.random() * 20, //重新設定每個像素的位置資訊

          fillStyle:

            "rgba(" +

            data[pos] +

            "," +

            data[pos + 1] +

            "," +

            data[pos + 2] +

            "," +

            data[pos + 3] +

            ")"

        };

        pixels.push(pixel);

      }

    }

  }

}

function drawPixels() {

  var canvas = document.getElementById("myCanvas");

  var ctx = canvas.getContext("2d");

  ctx.clearRect(0, 0, canvas.width, canvas.height);

  var len = pixels.length,

    curr_pixel = null;

  for (var i = 0; i < len; i++) {

    curr_pixel = pixels[i];

    ctx.fillStyle = curr_pixel.fillStyle;

    ctx.fillRect(curr_pixel.x, curr_pixel.y, 1, 1);

  }

}

粒子時鐘

渲染文字時鐘

function time() {

  ctx.clearRect(0, 0, canvas.width, canvas.height);

  ctx.font = "150px 黑體";

  ctx.textBaseline = "top";

  ctx.fillStyle = "rgba(245,245,245,0.2)";

  ctx.fillText(

    new Date().format("hh:mm:ss"),

    (canvas.width - textWidth) / 2,

    (canvas.height - textHeight) / 2,

    textWidth,

    textHeight

  );

}

效果

【HTML】109-canvas 中普通動效與粒子動效的實作

擷取粒子

文字轉換粒子概念同上,擷取標明區域的像素,根據篩選條件進行選擇并存入數組。經過周遊後重新繪制。

function getPixels() {

  let imgData = ctx.getImageData(

    (canvas.width - textWidth) / 2,

    (canvas.height - textHeight) / 2,

    textWidth,

    textHeight

  );

  let data = imgData.data;

  pixelsArr = [];

  for (let i = 1; i <= textHeight; i++) {

    for (let j = 1; j <= textWidth; j++) {

      pos = [(i - 1) * textWidth + (j - 1)] * 4; //取得像素位置

      if (data[pos] >= 0) {

        var pixel = {

          x: j + Math.random() * 20, //重新設定每個像素的位置資訊

          y: i + Math.random() * 20, //重新設定每個像素的位置資訊

          fillStyle:

            "rgba(" +

            data[pos] +

            "," +

            data[pos + 1] +

            "," +

            data[pos + 2] +

            "," +

            data[pos + 3] +

            ")"

        };

        pixelsArr.push(pixel);

      }

    }

  }

}

imgData儲存了所選區域内的像素資訊,每個像素點占據 4 位,儲存了 RGBA 四位資訊。篩選每個像素的第四位,這段代碼中将所有透明度不為 0 的像素都儲存到了數組pixelsArr中。

x、y記載了該粒子的位置資訊,為了産生效果圖中的運動效果,給每個粒子添加了 0-20 個像素的偏移位置,每次重繪時,偏移位置随機生成,産生運動效果。

粒子重繪

擷取粒子之後,需要清除畫布中原有的文字,将擷取到的粒子重新繪制到畫布上去。

function drawPixels() {

  // 清除畫布内容,進行重繪

  ctx.clearRect(0, 0, canvas.width, canvas.height);

  for (let i in pixelsArr) {

    ctx.fillStyle = pixelsArr[i].fillStyle;

    let r = Math.random() * 4;

    ctx.fillRect(pixelsArr[i].x, pixelsArr[i].y, r, r);

  }

}

粒子重繪時的樣式為篩選像素時原本的顔色與透明度,并且每個在畫布上繪制每個粒子時,定義大小參數 r,r 取值為 0-4 中随機的數字。最終生成的