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>ไม่ว่าจะใช้เส้นเดียวหรือหลายเส้น ขั้นตอนก็ยังคงเหมือนเดิม
- ctx.beginPath() เริ่มต้นรูปทรงของคุณ
- ctx.moveTo(x, y) กำหนดจุดแรก
- ctx.lineTo(x, y) กำหนดจุดถัดไปและเส้นที่ลากกลับไปยังจุดแรก ใช้ lineTo หลายครั้งเพื่อวาดรูปทรงที่ใหญ่ขึ้น
- กำหนด ctx.strokeStyle และ/หรือ ctx.fillStyle (หากเป็นรูปทรงปิด)
- ใช้ 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 ของเรา!