Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> JavaScript

Zoom HTML5 Canvas theo con trỏ chuột


Nguyên lý zoom canvas tại vị trí con trỏ

Mặc định canvas scale từ gốc tọa độ (0,0). Để zoom tại vị trí chuột, cần dịch chuyển gốc tọa độ (translate) đến vị trí chuột, scale, sau đó dịch ngược lại.

Công thức toán học

Thứ tự biến đổi quan trọng: Translate → Scale → Translate ngược

ctx.translate(mouseX, mouseY);    // Dịch gốc về vị trí chuột
ctx.scale(scaleFactor, scaleFactor); // Phóng to/thu nhỏ
ctx.translate(-mouseX, -mouseY);   // Dịch ngược về vị trí ban đầu

Ví dụ hoàn chỉnh

<!DOCTYPE html>
<html>
<body>
  <canvas id="myCanvas" width="800" height="600" style="border:1px solid #ccc;"></canvas>
  <script>
    const canvas = document.getElementById('myCanvas');
    const ctx = canvas.getContext('2d');
    
    let scale = 1;
    const minScale = 0.1;
    const maxScale = 10;
    const scaleStep = 0.1;
    
    // Vẽ nội dung mẫu
    function drawContent() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.save();
      ctx.translate(offsetX, offsetY);
      ctx.scale(scale, scale);
      
      // Vẽ lưới tham chiếu
      ctx.strokeStyle = '#eee';
      ctx.lineWidth = 1;
      for(let i = 0; i < 100; i++) {
        ctx.beginPath();
        ctx.moveTo(i * 50, 0);
        ctx.lineTo(i * 50, 5000);
        ctx.stroke();
        ctx.beginPath();
        ctx.moveTo(0, i * 50);
        ctx.lineTo(5000, i * 50);
        ctx.stroke();
      }
      
      // Vẽ hình mẫu
      ctx.fillStyle = '#3498db';
      ctx.fillRect(100, 100, 200, 150);
      ctx.fillStyle = '#e74c3c';
      ctx.beginPath();
      ctx.arc(500, 300, 80, 0, Math.PI * 2);
      ctx.fill();
      
      ctx.restore();
    }
    
    let offsetX = 0, offsetY = 0;
    let isPanning = false;
    let lastX = 0, lastY = 0;
    
    // Xử lý zoom bằng cuộn chuột
    canvas.addEventListener('wheel', function(e) {
      e.preventDefault();
      
      const rect = canvas.getBoundingClientRect();
      const mouseX = e.clientX - rect.left;
      const mouseY = e.clientY - rect.top;
      
      // Tính toán offset mới để giữ vị trí chuột cố định
      const newScale = Math.max(minScale, Math.min(maxScale, scale - e.deltaY * 0.001 * scale));
      const scaleRatio = newScale / scale;
      
      offsetX = mouseX - (mouseX - offsetX) * scaleRatio;
      offsetY = mouseY - (mouseY - offsetY) * scaleRatio;
      scale = newScale;
      
      drawContent();
    }, { passive: false });
    
    // Xử lý pan (kéo thả)
    canvas.addEventListener('mousedown', function(e) {
      isPanning = true;
      lastX = e.clientX;
      lastY = e.clientY;
      canvas.style.cursor = 'grabbing';
    });
    
    window.addEventListener('mousemove', function(e) {
      if(!isPanning) return;
      const dx = e.clientX - lastX;
      const dy = e.clientY - lastY;
      offsetX += dx;
      offsetY += dy;
      lastX = e.clientX;
      lastY = e.clientY;
      drawContent();
    });
    
    window.addEventListener('mouseup', function() {
      isPanning = false;
      canvas.style.cursor = 'grab';
    });
    
    // Khởi tạo
    canvas.style.cursor = 'grab';
    drawContent();
  </script>
</body>
</html>

Giải thích kỹ thuật

Ma trận biến đổi (Transformation Matrix)

Mỗi lần gọi translate() và scale() sẽ nhân ma trận biến đổi hiện tại. Thứ tự thực hiện ảnh hưởng trực tiếp đến kết quả.

Cách tính offset khi zoom

Để giữ điểm dưới con trỏ chuột không di chuyển trên màn hình:

newOffset = mousePos - (mousePos - oldOffset) * (newScale / oldScale)

Mở rộng: Sử dụng thư viện

Để dự án phức tạp, cân nhắc các thư viện:

  • Fabric.js - Hỗ trợ zoom/pan built-in
  • Konva.js - Effectiveness cao cho canvas 2D
  • PanZoom - Thư viện nhẹ chuyên biệt cho zoom/pan