跳到內容

HTML5+JS

特效是使用原生 JavaScript 在 HTML5 canvas 元素上建立的。在本節中,我們將介紹基礎知識:繪製形狀使用迭代繪製,以及添加動態效果

在本教學中,我將使用一個通用的 Lightscript 範本:

<head>
<meta description="Template"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// 從 DOM 取得 canvas 元素
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
function update() {
// 程式碼在此
window.requestAnimationFrame(update);
}
window.requestAnimationFrame(update);
</script>

無論使用一條線還是多條線,過程都相同。

  1. ctx.beginPath() 開始您的形狀
  2. ctx.moveTo(x, y) 設定第一個點
  3. ctx.lineTo(x, y) 設定下一個點並繪製回第一個點的線條。使用多個 lineTo 來繪製更大的形狀。
  4. 設定 ctx.strokeStyle 和/或 ctx.fillStyle(如果形狀是封閉的)
  5. 使用 ctx.stroke() 繪製線條,使用 ctx.fill() 填充任何封閉形狀。需要注意的是,在此之前一切都是假設性的——直到執行這些命令才會真正繪製出來。

以下是三個使用線條的範例:

function update() {
// 單條線
ctx.beginPath();
ctx.moveTo(30, 30);
ctx.lineTo(30, 170);
ctx.strokeStyle = "blue";
ctx.stroke();
// L 形
ctx.beginPath();
ctx.moveTo(80, 30);
ctx.lineTo(80, 100);
ctx.lineTo(120, 100);
ctx.strokeStyle = "blue";
ctx.stroke();
// 填充三角形
ctx.beginPath();
ctx.moveTo(130, 30);
ctx.lineTo(130, 100);
ctx.lineTo(170, 100);
ctx.lineTo(130, 30);
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.fill();
ctx.stroke();
window.requestAnimationFrame(update);
}

請注意這裡的繪製順序:填充三角形最後繪製,因此如果它們在相同位置,它會覆蓋其他兩個形狀。此外,三角形的描邊在填充之後繪製。如果我們顛倒這些步驟,描邊的內部一半將被紅色填充覆蓋。

矩形簡單且實用,尤其對於 RGB 鍵盤的平均解析度而言。過程與繪製線條類似——只需將 moveTolineTo 命令替換為 rect(x, y, width, height)

function update() {
// 基本正方形
ctx.beginPath();
ctx.rect(30, 30, 75, 75);
ctx.strokeStyle = "blue";
ctx.stroke();
// 填充矩形
ctx.beginPath();
ctx.rect(125, 30, 125, 100);
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.fill();
ctx.stroke();
window.requestAnimationFrame(update);
}

弧形是透過將外點圍繞中心點旋轉來繪製的,可用於建立完整的圓形。弧形命令是 ctx.arc(x, y, radius, start angle, end angle),其中兩個角度值都以弧度表示。與矩形不同,弧形的原點 (x, y) 在弧形的中心

function update() {
// 半圓
ctx.beginPath();
ctx.arc(70, 100, 30, 0, Math.PI);
ctx.strokeStyle = "blue";
ctx.stroke();
// 3/4 圓
ctx.beginPath();
ctx.arc(140, 100, 30, 0, Math.PI * (3/2));
ctx.strokeStyle = "blue";
ctx.stroke();
// 完整圓形
ctx.beginPath();
ctx.arc(210, 100, 30, 0, Math.PI * 2);
ctx.strokeStyle = "blue";
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
window.requestAnimationFrame(update);
}

如您所見,弧形預設按順時針方向繪製。這可以透過使用小於起始角度的結束角度來反轉,或在 ctx.arc() 方法中添加 “true” 作為最後一個參數。

手動繪製三個形狀可能輕易佔用二十行程式碼,因此如果您需要繪製一百個形狀,我們需要更有效率的方法。JavaScript 中的迴圈是重複任務的簡單方式,也可以應用於繪製。

以下是使用 for 迴圈繪製一排棋盤格正方形的範例:

function update() {
for(let i = 0; i < 8; i++){
// 1. 為每個正方形建立矩形路徑並繪製描邊。
ctx.beginPath();
ctx.rect(i * 20, 20, 20, 20);
ctx.strokeStyle = "black";
ctx.stroke();
// 2. 如果 'i' 為偶數,fillStyle 為黑色。否則為白色。
if(i % 2 == 0){
ctx.fillStyle = "black";
} else {
ctx.fillStyle = "white";
}
// 3. 設定 fillStyle 後填充形狀。
ctx.fill();
}
window.requestAnimationFrame(update);
}

以下是一個 while 迴圈範例,用於繪製棋盤格。我將在每次迭代中稍微改變黑色正方形的亮度和白色正方形的色相,使用範本字面值。

function update() {
var i = 0;
while(i < 16){
// 1. 使用 'row' 和 'column' 計算 X 和 Y
// 此操作將結果向下捨入到最接近的整數。對於 i = (0-3),行將為 0,對於 i = (4-7),行將為 1,依此類推
var iRow = Math.floor(i / 4);
// 此操作找到除法後的餘數,將列限制在 (0-3) 範圍內。
var iCol = i % 4;
// 乘以正方形寬度以找到此正方形的 x 原點
var ix = iCol * 20;
// 乘以正方形高度以找到此正方形的 y 原點
var iy = iRow * 20;
// 2. 路徑和描邊
ctx.beginPath();
ctx.rect(ix, iy, 20, 20);
ctx.strokeStyle = "black";
ctx.stroke();
// 3. 如果行為偶數,每隔一個正方形填充黑色。如果為奇數,切換黑色正方形。
// 每次迭代都會在黑色的亮度分量和白色的色相中添加少量值。
if(iRow % 2 == 0){
if(i % 2 == 0){
// '黑色'
ctx.fillStyle = `hsl(1, 0%, ${5 * i}%)`
} else {
// '白色'
ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`;
}
} else {
if(i % 2 == 0){
// '白色'
ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`;
} else {
// '黑色'
ctx.fillStyle = `hsl(1, 0%, ${5 * i}%)`
}
}
ctx.fill();
i++;
}
window.requestAnimationFrame(update);
}

canvas 中的動畫只受您的想像力和數學知識的限制。如果您對三角學或幾何學感到不確定,我建議您做一些輕度的研究以提升技能。在接下來的幾個範例中,我將取一個靜止的圓形,並將每個可能的變數附加到一個簡單的振盪運動上。

振盪(規律的來回運動)的黃金標準是取定時器的 sin()cosine()無論傳入任何值,這兩個操作都將返回 -1 到 1 之間的值,即使該值只是在遞增。兩者之間唯一的區別是它們略微不同步——當 cos 為 0 時,sin 為 -1 或 1,反之亦然。我們可以在這個動畫中利用這一點——

function update() {
// 以毫秒為單位取得當前時間,然後除以減慢動畫速度
let time = Date.now() / 100;
// 繪製背景,使舊形狀不會殘留
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
ctx.beginPath();
// 每次只使用一個範例
// 第 1 個範例 — 左右移動,y 為常數
ctx.arc(15 * Math.cos(time) + 160, 100, 30, 0, Math.PI * 2);
// 第 2 個範例 — 上下移動,x 為常數
ctx.arc(160, 15 * Math.sin(time) + 100, 30, 0, Math.PI * 2);
// 第 3 個範例 — 圓形移動,x 和 y 都是變數
ctx.arc(15 * Math.cos(time) + 160, 15 * Math.sin(time) + 100, 30, 0, Math.PI * 2);
ctx.fillStyle = "black";
ctx.fill();
window.requestAnimationFrame(update);
}

我們還有幾個屬性可以隨時間改變——圓形半徑、填充和描邊。我暫時不改變弧形角度以保持形狀簡單,因為其餘部分會相當炫目。

function update() {
let time = Date.now() / 100;
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
ctx.beginPath();
ctx.arc(15 * Math.cos(time) + 160, 15 * Math.sin(time) + 100, 30 + 15 * Math.sin(time), 0, Math.PI * 2);
ctx.fillStyle = `hsl(${180 + 180 * Math.cos(time)}, 100%, 50%)`;
ctx.fill();
ctx.strokeStyle = `hsl(${180 + 180 * Math.sin(time)}, 100%, 50%)`;
ctx.stroke();
window.requestAnimationFrame(update);
}

我們迄今建立的每個形狀的每個屬性都可以隨時間改變,而我製作的每個動畫都是上述技能的組合。如需製作可重用、高效動畫的更多詳細資訊,請查看我們的 Callbacks 頁面!