JavaScript Canvas isPointInPath 教程
最后修改于 2025 年 4 月 3 日
本教程将探讨 JavaScript 中 Canvas 的 isPointInPath 方法。此方法用于检测一个点是否在当前路径内,从而实现命中检测。它对于游戏和图表等交互式 Canvas 应用至关重要。
基本定义
isPointInPath 检查指定的坐标是否在当前路径内。如果点在路径内,则返回 true,否则返回 false。这对于检测 Canvas 元素的点击或悬停非常有用。
该方法有两种形式:isPointInPath(x, y) 和 isPointInPath(path, x, y, fillRule)。第二种形式用于 Path2D 对象,并可选择填充规则(nonzero 或 evenodd)。
isPointInPath 基本用法
此示例展示了如何检测矩形路径内的点击。
<!DOCTYPE html>
<html>
<head>
<title>Basic isPointInPath</title>
</head>
<body>
<canvas id="myCanvas" width="300" height="200"></canvas>
<p id="output">Click on the rectangle</p>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const output = document.getElementById('output');
// Draw rectangle
ctx.beginPath();
ctx.rect(50, 50, 200, 100);
ctx.fillStyle = 'lightblue';
ctx.fill();
ctx.stroke();
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (ctx.isPointInPath(x, y)) {
output.textContent = 'Clicked inside the rectangle!';
} else {
output.textContent = 'Clicked outside the rectangle';
}
});
</script>
</body>
</html>
此代码在 Canvas 上创建一个蓝色矩形。单击时,它会使用 isPointInPath 检查点击坐标是否在矩形路径内。
点击坐标相对于 Canvas 位置进行了调整。结果将显示在 Canvas 下方的段落元素中。
多形状检测
此示例演示了对多个形状的点击检测。
<!DOCTYPE html>
<html>
<head>
<title>Multiple Shapes Detection</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="300"></canvas>
<p id="output">Click on a shape</p>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const output = document.getElementById('output');
// Draw shapes
ctx.beginPath();
ctx.rect(50, 50, 100, 100);
ctx.fillStyle = 'lightgreen';
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.arc(250, 100, 50, 0, Math.PI * 2);
ctx.fillStyle = 'lightcoral';
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(300, 200);
ctx.lineTo(350, 250);
ctx.lineTo(250, 250);
ctx.closePath();
ctx.fillStyle = 'lightblue';
ctx.fill();
ctx.stroke();
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Check rectangle
ctx.beginPath();
ctx.rect(50, 50, 100, 100);
if (ctx.isPointInPath(x, y)) {
output.textContent = 'Clicked on the square';
return;
}
// Check circle
ctx.beginPath();
ctx.arc(250, 100, 50, 0, Math.PI * 2);
if (ctx.isPointInPath(x, y)) {
output.textContent = 'Clicked on the circle';
return;
}
// Check triangle
ctx.beginPath();
ctx.moveTo(300, 200);
ctx.lineTo(350, 250);
ctx.lineTo(250, 250);
ctx.closePath();
if (ctx.isPointInPath(x, y)) {
output.textContent = 'Clicked on the triangle';
return;
}
output.textContent = 'Clicked outside all shapes';
});
</script>
</body>
</html>
此示例绘制了三个形状:一个正方形、一个圆形和一个三角形。单击时,它会检查每个形状的路径以确定单击的是哪个。
对于每个形状,我们在检查 isPointInPath 之前会重新创建其路径。为了优化性能,一旦检测到命中,该方法就会立即返回。
使用 Path2D 对象
此示例展示了如何将 Path2D 对象与 isPointInPath 一起使用。
<!DOCTYPE html>
<html>
<head>
<title>Path2D with isPointInPath</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="300"></canvas>
<p id="output">Click on a shape</p>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const output = document.getElementById('output');
// Create Path2D objects
const starPath = new Path2D();
starPath.moveTo(100, 25);
starPath.lineTo(120, 75);
starPath.lineTo(175, 75);
starPath.lineTo(135, 100);
starPath.lineTo(150, 150);
starPath.lineTo(100, 125);
starPath.lineTo(50, 150);
starPath.lineTo(65, 100);
starPath.lineTo(25, 75);
starPath.lineTo(80, 75);
starPath.closePath();
const heartPath = new Path2D();
heartPath.moveTo(250, 75);
heartPath.bezierCurveTo(250, 37, 300, 25, 300, 75);
heartPath.bezierCurveTo(300, 125, 250, 150, 250, 175);
heartPath.bezierCurveTo(250, 150, 200, 125, 200, 75);
heartPath.bezierCurveTo(200, 25, 250, 37, 250, 75);
heartPath.closePath();
// Draw paths
ctx.fillStyle = 'pink';
ctx.fill(starPath);
ctx.stroke(starPath);
ctx.fillStyle = 'red';
ctx.fill(heartPath);
ctx.stroke(heartPath);
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (ctx.isPointInPath(starPath, x, y)) {
output.textContent = 'Clicked on the star';
} else if (ctx.isPointInPath(heartPath, x, y)) {
output.textContent = 'Clicked on the heart';
} else {
output.textContent = 'Clicked outside shapes';
}
});
</script>
</body>
</html>
此示例使用 Path2D 对象创建复杂形状(星形和心形)。Path2D 允许在不重新创建的情况下重用路径,用于命中检测。
isPointInPath 方法接受 Path2D 作为第一个参数。这使得代码比重新创建路径更简洁、更有效。
填充规则演示
此示例演示了不同填充规则对点检测的影响。
<!DOCTYPE html>
<html>
<head>
<title>Fill Rule with isPointInPath</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="300"></canvas>
<p id="output">Click inside the concentric circles</p>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const output = document.getElementById('output');
// Draw concentric circles
ctx.beginPath();
ctx.arc(150, 150, 100, 0, Math.PI * 2);
ctx.arc(150, 150, 50, 0, Math.PI * 2);
ctx.fillStyle = 'lightgray';
ctx.fill();
ctx.stroke();
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Recreate path for hit detection
ctx.beginPath();
ctx.arc(150, 150, 100, 0, Math.PI * 2);
ctx.arc(150, 150, 50, 0, Math.PI * 2);
// Check with different fill rules
const nonzero = ctx.isPointInPath(x, y, 'nonzero');
const evenodd = ctx.isPointInPath(x, y, 'evenodd');
output.textContent = `Nonzero: ${nonzero}, Evenodd: ${evenodd}`;
});
</script>
</body>
</html>
此示例展示了填充规则如何影响复杂路径中的点检测。我们绘制了两个同心圆,并使用两种规则检查点包含情况。
“nonzero”规则(默认)将中心视为在内部,而“evenodd”规则将其视为在外部。这表明填充规则如何改变自相交路径的命中检测行为。
带有命中检测的交互式绘图
此示例创建了一个带有形状选择功能的交互式绘图应用程序。
<!DOCTYPE html>
<html>
<head>
<title>Interactive Drawing with Hit Detection</title>
</head>
<body>
<canvas id="myCanvas" width="500" height="400"></canvas>
<div>
<button id="addRect">Add Rectangle</button>
<button id="addCircle">Add Circle</button>
<p id="output">Click on shapes to select them</p>
</div>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const output = document.getElementById('output');
const addRect = document.getElementById('addRect');
const addCircle = document.getElementById('addCircle');
let shapes = [];
let selectedShape = null;
class Shape {
constructor(path, type, color) {
this.path = path;
this.type = type;
this.color = color;
this.selected = false;
}
}
function drawShapes() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
shapes.forEach(shape => {
ctx.fillStyle = shape.selected ? 'yellow' : shape.color;
ctx.fill(shape.path);
ctx.stroke(shape.path);
// Draw selection indicator
if (shape.selected) {
ctx.strokeStyle = 'red';
ctx.lineWidth = 3;
ctx.stroke(shape.path);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
}
});
}
// Add rectangle button
addRect.addEventListener('click', () => {
const x = Math.random() * 350 + 50;
const y = Math.random() * 250 + 50;
const width = Math.random() * 100 + 50;
const height = Math.random() * 100 + 50;
const path = new Path2D();
path.rect(x, y, width, height);
const colors = ['lightblue', 'lightgreen', 'pink', 'lavender'];
const color = colors[Math.floor(Math.random() * colors.length)];
shapes.push(new Shape(path, 'rectangle', color));
drawShapes();
});
// Add circle button
addCircle.addEventListener('click', () => {
const x = Math.random() * 350 + 50;
const y = Math.random() * 250 + 50;
const radius = Math.random() * 50 + 25;
const path = new Path2D();
path.arc(x, y, radius, 0, Math.PI * 2);
const colors = ['lightcoral', 'lightseagreen', 'plum', 'wheat'];
const color = colors[Math.floor(Math.random() * colors.length)];
shapes.push(new Shape(path, 'circle', color));
drawShapes();
});
// Canvas click handler
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Deselect all first
shapes.forEach(shape => shape.selected = false);
selectedShape = null;
// Check shapes in reverse order (top to bottom)
for (let i = shapes.length - 1; i >= 0; i--) {
if (ctx.isPointInPath(shapes[i].path, x, y)) {
shapes[i].selected = true;
selectedShape