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

translate_demo.js
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_scale.js
// 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();
🎨 Demo tương tác: translate · rotate · scale

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.

save_restore.js
// 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);
🎨 Demo tương tác: save()/restore() lồng nhau — cây fractal

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.

fan_blades.js
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.

matrix_transform.js
// 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

Related Articles

Bài viết liên quan trong series

Lesson 4: Compositing, Transparency & Clipping in Canvas Bài 4: Compositing, Độ trong suốt & Clipping trong Canvas Lesson 2: Text Rendering & Image Drawing on Canvas Bài 2: Text Rendering & Image Drawing trên Canvas Back to Canvas Series Overview Quay lại Lộ trình Canvas Series