ข้ามไปยังเนื้อหา

HTML5+JS

เอฟเฟกต์สร้างขึ้นด้วย JavaScript ล้วน (vanilla JavaScript) บนองค์ประกอบ canvas ของ HTML5 ในส่วนนี้ เราจะครอบคลุมพื้นฐาน ได้แก่ การวาดรูปทรง การวาดด้วยการวนซ้ำ และ การเพิ่มการเคลื่อนไหว

สำหรับบทช่วยสอนนี้ เราจะใช้เทมเพลต 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>
// Get the canvas element from the DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
function update() {
// Code goes here
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() {
// One Line
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();
// Filled Triangle
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 ขั้นตอนคล้ายกับการวาดเส้น เพียงแทนที่คำสั่ง moveTo และ lineTo ด้วย rect(x, y, width, height)

function update() {
// Basic Square
ctx.beginPath();
ctx.rect(30, 30, 75, 75);
ctx.strokeStyle = "blue";
ctx.stroke();
// Filled Rectangle
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() {
// Half-circle
ctx.beginPath();
ctx.arc(70, 100, 30, 0, Math.PI);
ctx.strokeStyle = "blue";
ctx.stroke();
// 3/4 circle
ctx.beginPath();
ctx.arc(140, 100, 30, 0, Math.PI * (3/2));
ctx.strokeStyle = "blue";
ctx.stroke();
// Full circle
ctx.beginPath();
ctx.arc(210, 100, 30, 0, Math.PI * 2);
ctx.strokeStyle = "blue";
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
window.requestAnimationFrame(update);
}

ดังที่คุณเห็น ส่วนโค้งจะถูกวาดตามเข็มนาฬิกาโดยค่าเริ่มต้น คุณสามารถกลับทิศได้โดยใช้มุมสิ้นสุดที่น้อยกว่ามุมเริ่มต้น หรือเพิ่ม “true” เป็นอาร์กิวเมนต์สุดท้ายให้กับเมธอด ctx.arc()

การวาดรูปทรงสามรูปด้วยมืออาจใช้โค้ดถึงยี่สิบบรรทัดได้ง่าย ๆ ดังนั้นหากคุณต้องวาดรูปทรงหนึ่งร้อยรูป เราจำเป็นต้องมีวิธีที่มีประสิทธิภาพมากกว่านี้ ลูปใน JavaScript เป็นวิธีง่าย ๆ ในการทำงานซ้ำ และสามารถนำมาใช้กับการวาดได้เช่นกัน

ต่อไปนี้เป็นตัวอย่างการใช้ for-loop เพื่อวาดแถวของสี่เหลี่ยมลายตารางหมากรุก:

function update() {
for(let i = 0; i < 8; i++){
// 1. Create rectangle path and draw the stroke for each.
ctx.beginPath();
ctx.rect(i * 20, 20, 20, 20);
ctx.strokeStyle = "black";
ctx.stroke();
// 2. If 'i' is even, the fillStyle is black. Otherwise it's white.
if(i % 2 == 0){
ctx.fillStyle = "black";
} else {
ctx.fillStyle = "white";
}
// 3. Fill the shape after setting the fillStyle.
ctx.fill();
}
window.requestAnimationFrame(update);
}

ต่อไปนี้เป็นตัวอย่าง while-loop เพื่อวาดตารางลายหมากรุก เราจะเปลี่ยนความสว่างของช่องสีดำและเฉดสีของช่องสีขาวเล็กน้อยในแต่ละรอบโดยใช้ template literals

function update() {
var i = 0;
while(i < 16){
// 1. X and Y calculations with 'row' and 'column'
// This operation rounds the result down to the nearest integer. For i = (0-3) the row will be 0, for i = (4-7) the row will be 1, and so on
var iRow = Math.floor(i / 4);
// This operation finds the remainder after division, limiting column to the (0-3) range.
var iCol = i % 4;
// Multiply by the square width to find this square's x-origin
var ix = iCol * 20;
// Multiply by the square height to find this square's y-origin
var iy = iRow * 20;
// 2. Path and stroke
ctx.beginPath();
ctx.rect(ix, iy, 20, 20);
ctx.strokeStyle = "black";
ctx.stroke();
// 3. If the row is even, every other square is filled black. If odd, switch the black squares. Each iteration adds a small amount of the lightness component to the black, and hue to the white.
if(iRow % 2 == 0){
if(i % 2 == 0){
// 'Black'
ctx.fillStyle = `hsl(1, 0%, ${5 * i}%)`
} else {
// 'White'
ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`;
}
} else {
if(i % 2 == 0){
// 'White'
ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`;
} else {
// 'Black'
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() {
// Find current time in milliseconds, then divide to slow down the animation speed
let time = Date.now() / 100;
// Draw a background so the old shapes don't stick around
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
ctx.beginPath();
//USE ONE EXAMPLE AT A TIME
// 1st example - side to side, y is constant
ctx.arc(15 * Math.cos(time) + 160, 100, 30, 0, Math.PI * 2);
// 2nd example - up and down, x is constant
ctx.arc(160, 15 * Math.sin(time) + 100, 30, 0, Math.PI * 2);
// 3rd example - circle, x and y are variable
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 ของเรา!