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