This programming guide is only available in Vietnamese. Switch to Vietnamese to read the full article.
Transforms là công cụ mạnh mẽ cho phép bạn dịch chuyển, xoay và co giãn hệ tọa độ Canvas. Kết hợp với save/restore state stack, bạn có thể vẽ các đối tượng phức tạp một cách có tổ chức và dễ quản lý.
1. translate(x, y)
translate() dịch chuyển gốc tọa độ (origin) đến vị trí mới. Thay vì offset mọi coordinates khi vẽ, bạn translate origin rồi vẽ tại (0,0).
const ctx = canvas.getContext('2d');
// Không dùng translate — phải offset thủ công
ctx.fillStyle = '#3b82f6';
ctx.fillRect(100, 100, 50, 50);
// Dùng translate — vẽ tại origin, translate đến vị trí cần
ctx.translate(250, 100);
ctx.fillStyle = '#e11d48';
ctx.fillRect(0, 0, 50, 50); // vẽ tại (0,0) nhưng hiện ở (250,100)
// LƯU Ý: translate CỘNG DỒN!
ctx.translate(100, 0); // bây giờ origin ở (350, 100)
ctx.fillStyle = '#22c55e';
ctx.fillRect(0, 0, 50, 50); // hiện ở (350, 100)
2. rotate(angle) & scale(sx, sy)
rotate() xoay hệ tọa độ quanh origin hiện tại (theo radians). scale() co giãn theo trục x và y. Quan trọng: transforms áp dụng cho TẤT CẢ drawing sau đó.
// Rotate — quanh origin, đơn vị RADIANS
// Công thức: radians = degrees * Math.PI / 180
ctx.save();
ctx.translate(150, 150); // di chuyển origin đến tâm
ctx.rotate(Math.PI / 4); // xoay 45 độ
ctx.fillStyle = '#f59e0b';
ctx.fillRect(-40, -40, 80, 80); // vẽ hình vuông quanh tâm
ctx.restore();
// Scale — co giãn theo trục
ctx.save();
ctx.translate(350, 150);
ctx.scale(2, 0.5); // gấp đôi chiều ngang, nửa chiều dọc
ctx.fillStyle = '#8b5cf6';
ctx.fillRect(-30, -30, 60, 60); // trông như hình chữ nhật
ctx.restore();
// Scale âm = flip (lật)
ctx.save();
ctx.translate(500, 150);
ctx.scale(-1, 1); // flip ngang (mirror)
ctx.fillStyle = '#06b6d4';
ctx.font = '24px Arial';
ctx.fillText('Mirror', 0, 0); // text bị lật ngược
ctx.restore();
3. save() & restore()
save() push toàn bộ state hiện tại (transforms, styles, clipping) vào stack. restore() pop state gần nhất. Đây là pattern quan trọng nhất khi dùng transforms.
// Pattern: save → transform → draw → restore
ctx.fillStyle = '#fff';
ctx.font = '16px Arial';
// State gốc
ctx.save(); // ── push state 1 ──
ctx.translate(100, 100);
ctx.fillStyle = '#e11d48';
ctx.fillRect(0, 0, 50, 50);
ctx.save(); // ── push state 2 (nested) ──
ctx.translate(80, 0);
ctx.rotate(Math.PI / 6);
ctx.fillStyle = '#3b82f6';
ctx.fillRect(0, 0, 50, 50);
ctx.restore(); // ── pop state 2 → về state 1 ──
// Ở đây: origin = (100,100), fillStyle = '#e11d48'
ctx.fillRect(0, 70, 50, 50);
ctx.restore(); // ── pop state 1 → về state gốc ──
// Ở đây: origin = (0,0), fillStyle = '#fff'
ctx.fillText('Back to original state', 10, 30);
4. Transform Composition
Kết hợp translate + rotate + scale để vẽ đối tượng phức tạp. Ví dụ kinh điển: vẽ cánh quạt quay.
function drawFan(ctx, cx, cy, blades, angle) {
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(angle);
for (let i = 0; i < blades; i++) {
ctx.save();
ctx.rotate((Math.PI * 2 / blades) * i);
// Vẽ 1 cánh quạt
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(10, -80);
ctx.lineTo(-10, -80);
ctx.closePath();
ctx.fillStyle = `hsl(${(360 / blades) * i}, 70%, 60%)`;
ctx.fill();
ctx.restore();
}
// Tâm quạt
ctx.beginPath();
ctx.arc(0, 0, 12, 0, Math.PI * 2);
ctx.fillStyle = '#333';
ctx.fill();
ctx.restore();
}
// Animation
let fanAngle = 0;
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
fanAngle += 0.05;
drawFan(ctx, 300, 200, 5, fanAngle);
requestAnimationFrame(animate);
}
animate();
5. setTransform & Matrix
setTransform(a, b, c, d, e, f) đặt trực tiếp ma trận biến đổi 2D affine. Không cộng dồn — thay thế hoàn toàn transform hiện tại.
// Ma trận biến đổi 2D:
// | a c e | | scaleX skewX translateX |
// | b d f | = | skewY scaleY translateY |
// | 0 0 1 | | 0 0 1 |
// setTransform — đặt matrix trực tiếp (THAY THẾ transform cũ)
ctx.setTransform(1, 0, 0, 1, 0, 0); // identity matrix (reset)
// Ví dụ: skew (nghiêng)
ctx.setTransform(
1, // a: scaleX
0.3, // b: skewY
-0.3, // c: skewX
1, // d: scaleY
100, // e: translateX
100 // f: translateY
);
ctx.fillStyle = '#e11d48';
ctx.fillRect(0, 0, 100, 60);
// getTransform — lấy matrix hiện tại
const matrix = ctx.getTransform();
console.log(matrix); // DOMMatrix { a, b, c, d, e, f }
// resetTransform — về identity matrix
ctx.resetTransform(); // equivalent: setTransform(1,0,0,1,0,0)
// transform() — NHÂN thêm matrix (cộng dồn, khác setTransform)
ctx.transform(1, 0, 0, 1, 200, 200); // translate
ctx.transform(
Math.cos(0.5), Math.sin(0.5), // rotation
-Math.sin(0.5), Math.cos(0.5),
0, 0
);
ctx.fillStyle = '#22c55e';
ctx.fillRect(-25, -25, 50, 50);
6. Câu hỏi trắc nghiệm ôn tập
Trắc nghiệm 1: Transform accumulation
Nếu gọi ctx.translate(100, 0) rồi ctx.translate(50, 0), origin ở đâu?
Trắc nghiệm 2: save/restore
save() lưu những gì vào stack?
Trắc nghiệm 3: setTransform vs transform
Sự khác biệt giữa setTransform() và transform()?
Tải file code thực hành minh họa bài học
File script tổng hợp các ví dụ về Canvas transforms, save/restore và matrix:
Tải về canvas_transforms.js
Comments
Bình luận