canvas 用于在網頁上繪制圖像、動畫,可以将其了解為畫布,在這個畫布上建構想要的效果。
canvas 可以繪制動态效果,除了常用的規則動畫之外,還可以采用粒子的概念來實作較複雜的動效,本文分别采用普通動效與粒子特效實作了一個簡單的時鐘。
普通時鐘
普通動效即利用 canvas 的 api,實作有規則的圖案、動畫。
效果
該效果實作比較簡單,主要分析一下刻度與指針角度偏移的實作。
繪制刻度
此例為小時刻度的繪制:表盤上共有 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中的每一個像素點,擷取到每個像素點之後,添加屬性或事件對區域内的粒子進行互動,達到動态效果。
效果
粒子擷取
以下圖的圖檔轉化為例,該效果是先在 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 資料。
粒子繪制
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
);
}
效果
擷取粒子
文字轉換粒子概念同上,擷取標明區域的像素,根據篩選條件進行選擇并存入數組。經過周遊後重新繪制。
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 中随機的數字。最終生成的