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

Xử lý nhiều sự kiện click trên các vùng khác nhau của HTML5 Canvas (Path2D)

Canvas không có DOM cho từng hình vẽ. Để xử lý click trên các vùng khác nhau (ví dụ: nửa trên click gọi func1, nửa dưới gọi func2), dùng Path2D để lưu đường vẽ và isPointInPath() để hit-test.

Cơ chế: Path2D + Hit Testing

  1. Tạo Path2D object cho từng vùng clickable.
  2. Vẽ các path đó lên canvas (có thể style khác nhau).
  3. Gắn click event cho canvas.
  4. Trong handler: tính tọa độ click → lặp qua mảng path → ctx.isPointInPath(path, x, y).

Ví dụ hoàn chỉnh: Tròn chia 2 nửa màu, click mỗi nửa hành động khác

<!DOCTYPE html>
<html>
<body>
  <canvas id="myCanvas" width="400" height="400" style="border:1px solid #ccc; cursor:pointer"></canvas>
  <p id="status">Click vào nửa đỏ hoặc nửa xám</p>
  
  <script>
    var canvas = document.getElementById('myCanvas');
    var ctx = canvas.getContext('2d');
    var rect = canvas.getBoundingClientRect();
    var status = document.getElementById('status');
    
    // 1. Tạo Path2D cho từng vùng
    var centerX = 200, centerY = 200, radius = 100;
    
    // Nửa trên (đỏ): từ -90° đến 90° (theo radian: -PI/2 đến PI/2)
    var pathRed = new Path2D();
    pathRed.arc(centerX, centerY, radius, -Math.PI/2, Math.PI/2);
    pathRed.lineTo(centerX, centerY); // Đóng path về tâm
    pathRed.closePath();
    
    // Nửa dưới (xám): từ 90° đến 270° (PI/2 đến 3*PI/2)
    var pathGray = new Path2D();
    pathGray.arc(centerX, centerY, radius, Math.PI/2, 3*Math.PI/2);
    pathGray.lineTo(centerX, centerY);
    pathGray.closePath();
    
    var paths = [
      { path: pathRed, name: 'Nửa đỏ (trên)', action: function() { alert('Clicked RED half!'); } },
      { path: pathGray, name: 'Nửa xám (dưới)', action: function() { alert('Clicked GRAY half!'); } }
    ];
    
    // 2. Vẽ
    function render() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      
      // Vẽ nửa đỏ
      ctx.fillStyle = '#d43030';
      ctx.fill(pathRed);
      
      // Vẽ nửa xám
      ctx.fillStyle = '#b8b8b8';
      ctx.fill(pathGray);
      
      // Viền ngoài
      ctx.lineWidth = 2;
      ctx.strokeStyle = '#333';
      ctx.beginPath();
      ctx.arc(centerX, centerY, radius, 0, Math.PI*2);
      ctx.stroke();
    }
    render();
    
    // 3. Xử lý click
    canvas.addEventListener('click', function(e) {
      var x = e.clientX - rect.left;
      var y = e.clientY - rect.top;
      
      // Kiểm tra theo thứ tự ngược (trên cùng trước) - paths[0] là đỏ, paths[1] là xám
      for (var i = paths.length - 1; i >= 0; i--) {
        var item = paths[i];
        if (ctx.isPointInPath(item.path, x, y)) {
          status.textContent = 'Clicked: ' + item.name;
          item.action();
          break; // Chỉ trigger 1 vùng (trên cùng)
        }
      }
    });
  </script>
</body>
</html>

Mở rộng: Nhiều vùng bất kỳ (Polygon, Rect, Custom Path)

var clickableAreas = [
  { 
    path: new Path2D().rect(10, 10, 100, 50), 
    name: 'Button 1', 
    onClick: () => console.log('Btn 1') 
  },
  { 
    path: (function() { 
      var p = new Path2D();
      p.moveTo(200, 50);
      p.lineTo(250, 20);
      p.lineTo(300, 50);
      p.lineTo(250, 80);
      p.closePath();
      return p; // Diamond shape
    })(),
    name: 'Diamond',
    onClick: () => console.log('Diamond')
  }
];

// Trong click handler:
clickableAreas.forEach(function(area) {
  if (ctx.isPointInPath(area.path, x, y)) area.onClick();
});

Lưu ý quan trọng

  • isPointInPath() dùng transform matrix hiện tại của context. Nếu dùng ctx.translate/scale/rotate khi vẽ, phải apply cùng transform khi test.
  • Để test chính xác: ctx.isPointInPath(path, x, y) (tham số path tách biệt) - không bị ảnh hưởng bởi transform hiện tại.
  • Path2D không hỗ trợ hit-test cho text (text là glyph, không phải path). Cần measureText + bounding box.
  • Performance: Với hàng trăm path, cân nhắc spatial index (quadtree) hoặc dùng Konva.js/Fabric.js.