You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 boundary object to match your scene's actual limits (add Z-axis checks if your scene uses 3D movement).
  • Performance: Using useCallback for 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:42:46