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
- Tạo
Path2Dobject cho từng vùng clickable. - Vẽ các path đó lên canvas (có thể style khác nhau).
- Gắn
clickevent cho canvas. - 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ùngctx.translate/scale/rotatekhi 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.