Swift技术实现:如何利用Hit Test与Bezier Path识别人体部位点击?
Hey there! Let me walk you through building this interactive body part vocabulary tool that works seamlessly on both iPhone and iPad. I’ll break down the process into actionable steps with code examples you can copy-paste and tweak.
The key here is defining clickable "hotspots" on your static body image. When a user taps (or clicks) a hotspot, we’ll trigger a display of the corresponding foreign word. There are two reliable ways to do this—pick the one that fits your needs:
Option 1: Quick & Simple with HTML Image Maps
This is perfect if your body parts have straightforward shapes and you don’t need dynamic adjustments. We’ll use the <map> and <area> tags to define clickable regions.
Step-by-Step Implementation
- Prepare Responsive Images
Create 1x, 2x, and 3x versions of your body image (for Retina displays on iOS). Usesrcsetto serve the right resolution automatically. - Define Hotspots
Use an online image map generator (no external links needed—just search for one, upload your image, draw regions, and copy thecoordsvalues) to get the coordinates for each body part. - Add Interaction & Display
<!-- Responsive body image --> <img src="body-1x.png" srcset="body-2x.png 2x, body-3x.png 3x" alt="Human Body" usemap="#bodyMap" style="width: 100%; height: auto; max-width: 100%;" > <!-- Hotspot definitions --> <map name="bodyMap"> <!-- Arm region (polygon shape) --> <area shape="poly" coords="120,340, 180,320, 200,450, 140,470" alt="Arm" onclick="showVocab('Arm', 'brazo')" > <!-- Leg region (polygon shape) --> <area shape="poly" coords="220,500, 260,680, 300,680, 340,500" alt="Leg" onclick="showVocab('Leg', 'pierna')" > <!-- Hand region (circle shape) --> <area shape="circle" coords="190,460, 30" alt="Hand" onclick="showVocab('Hand', 'mano')" > </map> <!-- Vocab display container --> <div id="vocabDisplay" style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); padding: 15px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); font-size: 18px; display: none;"> <p><strong>Part:</strong> <span id="partName"></span></p> <p><strong>Foreign Word:</strong> <span id="foreignWord"></span></p> </div> <script> function showVocab(part, word) { const display = document.getElementById('vocabDisplay'); document.getElementById('partName').textContent = part; document.getElementById('foreignWord').textContent = word; display.style.display = 'block'; // Auto-hide after 3 seconds setTimeout(() => { display.style.display = 'none'; }, 3000); } // Fix iOS touch support for <area> tags document.querySelectorAll('area').forEach(area => { area.addEventListener('touchstart', function(e) { e.preventDefault(); // Prevent accidental scrolling this.onclick(); }); }); </script>
Option 2: Flexible JavaScript Coordinate Detection
Use this if you need dynamic hotspot scaling (e.g., your image resizes across different iOS devices) or have complex body part shapes. We’ll use relative coordinates so hotspots stay aligned no matter the image size.
Step-by-Step Implementation
- Same Responsive Image Setup
Usesrcsetfor high-res displays, and set the image to be width-responsive. - Define Relative Regions
Instead of fixed pixel coordinates, use ratios (0-1) relative to the image’s width/height. - Add Hit Detection Logic
<img src="body-1x.png" srcset="body-2x.png 2x, body-3x.png 3x" alt="Human Body" id="bodyImage" style="width: 100%; height: auto; max-width: 100%;" > <div id="vocabDisplay" style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); padding: 15px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); font-size: 18px; display: none;"> <p><strong>Part:</strong> <span id="partName"></span></p> <p><strong>Foreign Word:</strong> <span id="foreignWord"></span></p> </div> <script> const bodyImage = document.getElementById('bodyImage'); const vocabDisplay = document.getElementById('vocabDisplay'); const partName = document.getElementById('partName'); const foreignWord = document.getElementById('foreignWord'); // Define body parts with relative coordinates const bodyParts = [ { name: 'Arm', word: 'brazo', polygon: [[0.2, 0.4], [0.3, 0.35], [0.35, 0.6], [0.25, 0.65]] // [x-ratio, y-ratio] points }, { name: 'Leg', word: 'pierna', polygon: [[0.4, 0.65], [0.45, 0.9], [0.55, 0.9], [0.6, 0.65]] }, { name: 'Hand', word: 'mano', circle: [0.32, 0.62, 0.05] // [center-x, center-y, radius-ratio] } ]; // Helper: Check if point is inside polygon function pointInPolygon(point, polygon) { let inside = false; for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { const [xi, yi] = polygon[i]; const [xj, yj] = polygon[j]; const intersect = ((yi > point[1]) !== (yj > point[1])) && (point[0] < (xj - xi) * (point[1] - yi) / (yj - yi) + xi); if (intersect) inside = !inside; } return inside; } // Helper: Check if point is inside circle function pointInCircle(point, circle) { const dx = point[0] - circle[0]; const dy = point[1] - circle[1]; return dx*dx + dy*dy <= circle[2]*circle[2]; } // Handle clicks/touches function handleInteraction(e) { e.preventDefault(); const rect = bodyImage.getBoundingClientRect(); // Calculate relative click position (0-1 for x/y) const x = (e.type === 'touchstart' ? e.touches[0].clientX : e.clientX - rect.left) / rect.width; const y = (e.type === 'touchstart' ? e.touches[0].clientY : e.clientY - rect.top) / rect.height; // Check each body part for (const part of bodyParts) { let isHit = false; if (part.polygon) isHit = pointInPolygon([x, y], part.polygon); if (part.circle) isHit = pointInCircle([x, y], part.circle); if (isHit) { partName.textContent = part.name; foreignWord.textContent = part.word; vocabDisplay.style.display = 'block'; setTimeout(() => vocabDisplay.style.display = 'none', 3000); break; } } } // Bind events for both click and touch bodyImage.addEventListener('click', handleInteraction); bodyImage.addEventListener('touchstart', handleInteraction); </script>
Critical iOS Adaptation Tips
- Touch Event Support: Always add
touchstartlisteners alongsideclick—iOS devices prioritize touch events over mouse clicks. - Responsive Images: Use
srcsetto serve 2x/3x images for Retina screens, and setwidth: 100%; height: auto;to ensure the image scales correctly across iPhone and iPad. - Display Layout: Use fixed positioning for the vocab display to keep it accessible on both small and large screens. Add media queries to adjust font sizes:
@media (max-width: 768px) { /* iPhone */ #vocabDisplay { font-size: 16px; padding: 12px; } } @media (min-width: 769px) { /* iPad */ #vocabDisplay { font-size: 20px; padding: 18px; } }
内容的提问来源于stack exchange,提问作者devpreneur

