Bỏ qua để đến nội dung

HTML5+JS

Các hiệu ứng được tạo bằng cách sử dụng vanilla JavaScript trên phần tử HTML5 canvas. Trong phần này, chúng ta sẽ đề cập đến các kiến thức cơ bản: vẽ hình dạng, vẽ với vòng lặpthêm chuyển động.

Đối với hướng dẫn này, tôi sử dụng mẫu Lightscript chung:

<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>

Dù một hay nhiều đường, quy trình vẫn giống nhau.

  1. ctx.beginPath() bắt đầu hình dạng của bạn
  2. ctx.moveTo(x, y) đặt điểm đầu tiên
  3. ctx.lineTo(x, y) đặt điểm tiếp theo và đường trở lại điểm đầu. Sử dụng nhiều lời gọi lineTo để vẽ các hình dạng lớn hơn.
  4. Đặt ctx.strokeStyle và/hoặc ctx.fillStyle (nếu hình dạng đóng)
  5. Sử dụng ctx.stroke() để vẽ đường và ctx.fill() để tô màu các hình đóng. Điều quan trọng cần lưu ý là mọi thứ cho đến thời điểm này đều là giả thuyết — không có gì được vẽ cho đến khi các lệnh này được thực thi.

Đây là ba ví dụ với đường:

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);
}

Điều quan trọng cần chú ý là thứ tự vẽ ở đây: tam giác được tô màu được vẽ cuối cùng và sẽ che hai hình kia nếu chúng ở cùng vị trí. Ngoài ra, đường viền trong tam giác được vẽ sau khi tô màu. Nếu chúng ta đảo ngược các bước này, nửa bên trong của đường viền sẽ bị che bởi màu đỏ.

Hình chữ nhật đơn giản và hữu ích, đặc biệt cho độ phân giải trung bình của bàn phím RGB. Quy trình tương tự như vẽ đường — chỉ cần thay thế các lệnh moveTolineTo bằng 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);
}

Cung tròn được vẽ bằng cách xoay điểm ngoài xung quanh điểm trung tâm và có thể được sử dụng để tạo hình tròn đầy đủ. Lệnh vẽ cung là ctx.arc(x, y, radius, startAngle, endAngle), trong đó cả hai góc đều được biểu thị bằng radian. Không giống như hình chữ nhật, cung được vẽ với điểm gốc (x, y) ở trung tâm của cung.

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);
}

Như bạn có thể thấy, các cung mặc định được vẽ theo chiều kim đồng hồ. Điều này có thể được đảo ngược bằng cách đặt góc kết thúc nhỏ hơn góc bắt đầu, hoặc thêm “true” làm đối số cuối cùng của phương thức ctx.arc().

Vẽ thủ công ba hình dạng có thể dễ dàng tốn hai mươi dòng code. Vì vậy, nếu chúng ta cần vẽ một trăm hình, chúng ta cần một cách tiếp cận hiệu quả hơn. Vòng lặp trong JavaScript là cách đơn giản để lặp lại các tác vụ và có thể áp dụng cho việc vẽ.

Đây là ví dụ với vòng lặp for để vẽ một hàng ô bàn cờ:

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);
}

Đây là ví dụ với vòng lặp while để vẽ mẫu bàn cờ. Tôi sẽ thay đổi nhẹ độ sáng của các ô đen và sắc độ của các ô trắng trong mỗi lần lặp bằng 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);
}

Các animation trên canvas chỉ bị giới hạn bởi trí tưởng tượng và kiến thức toán học của bạn. Nếu bạn chưa tự tin về lượng giác hoặc hình học, tôi khuyên bạn nên nghiên cứu để nâng cao kỹ năng của mình lên một tầm cao mới. Trong các ví dụ sau đây, tôi lấy một vòng tròn đứng yên và gán chuyển động dao động đơn giản cho mỗi biến có thể.

Tiêu chuẩn vàng cho dao động (chuyển động qua lại thường xuyên) là lấy sin() hoặc cos() của bộ hẹn giờ. Mỗi thao tác trả về giá trị từ -1 đến 1 cho MỌI giá trị được truyền vào, ngay cả khi nó chỉ đếm lên. Sự khác biệt duy nhất giữa hai hàm là chúng hơi lệch pha — khi cos bằng 0, sin bằng -1 hoặc 1 và ngược lại. Chúng ta có thể tận dụng điều này trong animation này:

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);
}

Chúng ta có thêm một vài thuộc tính có thể thay đổi theo thời gian — bán kính vòng tròn, màu tô và đường viền. Tôi sẽ để các góc cung không thay đổi để giữ hình dạng đơn giản, vì phần còn lại sẽ khá ấn tượng.

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);
}

Mỗi thuộc tính của mỗi hình dạng chúng ta đã tạo cho đến nay có thể thay đổi theo thời gian, và mỗi animation tôi tạo ra đều là sự kết hợp của các kỹ năng đã đề cập ở trên. Để biết thêm chi tiết về việc tạo các animation có thể tái sử dụng, hiệu quả, hãy xem trang Callbacks của chúng tôi!