Python Selenium:Three.js/React/Canvas中拖拽球体越界Tooltip实现
Alright, let's tackle this tricky interaction problem step by step. I've worked with similar Three.js + React setups before, so here's a practical approach that ties everything to React's state management seamlessly:
Solution Approach
First, let's outline the core logic we need to implement:
- Track when the sphere is being dragged
- Detect if the sphere crosses the boundary during drag
- Show/hide the tooltip based on boundary status and mouse events
- Keep all state managed by React to ensure UI updates are synced
Step-by-Step Implementation
1. Set Up React State
We'll need state variables to track the key parts of our interaction:
import { useState, useEffect, useRef, useCallback } from 'react'; import * as THREE from 'three'; const SphereDragComponent = () => { // Track if we're actively dragging the sphere const [isDragging, setIsDragging] = useState(false); // Control tooltip visibility const [showTooltip, setShowTooltip] = useState(false); // Store the sphere's current position for state sync const [spherePosition, setSpherePosition] = useState({ x: 0, y: 0, z: 0 }); // Track tooltip position to follow the mouse cursor const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }); // Refs for Three.js core objects (avoids reinitializing on renders) const sceneRef = useRef(null); const cameraRef = useRef(null); const rendererRef = useRef(null); const sphereRef = useRef(null); const raycasterRef = useRef(new THREE.Raycaster()); const mouseRef = useRef(new THREE.Vector2());
2. Initialize Three.js Scene
Use useEffect to set up the scene, camera, renderer, and sphere when the component mounts:
useEffect(() => { // Initialize scene const scene = new THREE.Scene(); sceneRef.current = scene; // Camera setup (adjust position based on your scene) const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 5; cameraRef.current = camera; // Renderer linked to our canvas element const renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas') }); renderer.setSize(window.innerWidth, window.innerHeight); rendererRef.current = renderer; // Create the draggable sphere const geometry = new THREE.SphereGeometry(0.5, 32, 32); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); const sphere = new THREE.Mesh(geometry, material); scene.add(sphere); sphereRef.current = sphere; // Animation loop to keep the scene rendering const animate = () => { requestAnimationFrame(animate); renderer.render(scene, camera); }; animate(); // Cleanup resources when component unmounts return () => { renderer.dispose(); geometry.dispose(); material.dispose(); }; }, []);
3. Implement Drag & Boundary Detection Logic
We'll add event listeners to handle mouse interactions, using useCallback to prevent unnecessary re-renders:
// Define your boundary limits (adjust these to match your scene's constraints) const boundary = { x: [-2, 2], y: [-2, 2] }; // Handle mouse down to start dragging the sphere const handleMouseDown = useCallback((event) => { // Convert mouse position to Three.js normalized device coordinates mouseRef.current.x = (event.clientX / window.innerWidth) * 2 - 1; mouseRef.current.y = -(event.clientY / window.innerHeight) * 2 + 1; // Raycast to check if we clicked directly on the sphere raycasterRef.current.setFromCamera(mouseRef.current, cameraRef.current); const intersects = raycasterRef.current.intersectObject(sphereRef.current); if (intersects.length > 0) { setIsDragging(true); } }, []); // Handle mouse move during drag to update position and check boundaries const handleMouseMove = useCallback((event) => { if (!isDragging) return; // Update tooltip position to follow the mouse setTooltipPosition({ x: event.clientX + 10, y: event.clientY + 10 }); // Convert mouse position to world coordinates mouseRef.current.x = (event.clientX / window.innerWidth) * 2 - 1; mouseRef.current.y = -(event.clientY / window.innerHeight) * 2 + 1; raycasterRef.current.setFromCamera(mouseRef.current, cameraRef.current); // Calculate intersection with the XY plane (adjust if using a different plane) const plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0); const intersectionPoint = new THREE.Vector3(); raycasterRef.current.ray.intersectPlane(plane, intersectionPoint); // Update sphere position in the scene and React state sphereRef.current.position.set(intersectionPoint.x, intersectionPoint.y, 0); setSpherePosition({ x: intersectionPoint.x, y: intersectionPoint.y, z: 0 }); // Check if the sphere is outside the defined boundary const isOutside = intersectionPoint.x < boundary.x[0] || intersectionPoint.x > boundary.x[1] || intersectionPoint.y < boundary.y[0] || intersectionPoint.y > boundary.y[1]; setShowTooltip(isOutside); }, [isDragging]); // Handle mouse up/leave to end dragging and hide tooltip const handleMouseUp = useCallback(() => { setIsDragging(false); setShowTooltip(false); }, []); // Attach event listeners to the canvas and window useEffect(() => { const canvas = document.getElementById('three-canvas'); canvas.addEventListener('mousedown', handleMouseDown); window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); window.addEventListener('mouseleave', handleMouseUp); // Cleanup listeners on unmount return () => { canvas.removeEventListener('mousedown', handleMouseDown); window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); window.removeEventListener('mouseleave', handleMouseUp); }; }, [handleMouseDown, handleMouseMove, handleMouseUp]);
4. Render the Canvas & Tooltip
Finally, render the Three.js canvas and the tooltip conditionally based on our state:
return ( <div style={{ position: 'relative', width: '100%', height: '100vh' }}> <canvas id="three-canvas" style={{ width: '100%', height: '100%' }} /> {showTooltip && ( <div style={{ position: 'absolute', top: `${tooltipPosition.y}px`, left: `${tooltipPosition.x}px`, background: '#ff4444', color: 'white', padding: '4px 8px', borderRadius: '4px', fontSize: '14px', pointerEvents: 'none' // Prevent tooltip from blocking mouse interactions }} > *Sphere is outside boundary!* </div> )} </div> ); }; export default SphereDragComponent;
Key Notes
- Boundary Adjustment: Modify the
boundaryobject to match your scene's actual limits (add Z-axis checks if your scene uses 3D movement). - Performance: Using
useCallbackfor event handlers ensures we don't create new functions on every render, which helps maintain smooth interaction. - Tooltip Positioning: The
pointerEvents: 'none'style on the tooltip is crucial to avoid it intercepting mouse events during drag. - Raycasting: We use raycasting to accurately detect clicks on the sphere and calculate the drag position relative to the scene's coordinate system.
内容的提问来源于stack exchange,提问作者Addison Joseph
相关产品推荐
相关产品推荐

