Threejs Interaction

CloudAI-X/threejs-skills/skills/threejs-interaction

作者 CloudAI-Xb1c623076c661fc9b03dac19292e825a5d106823無授權條款3.4K 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫3 個月前更新

Three.js interaction - raycasting, controls, mouse/touch input, object selection. Use when handling user input, implementing click detection, adding camera controls, or creating interactive 3D experiences.

AI 產生的概覽

Three.js 使用者互動參考指南:射線偵測、相機控制、選取與輸入處理。

功能
提供在 Three.js 場景中處理使用者輸入的程式碼片段與模式,包括用於點擊與懸停偵測的射線偵測、滑鼠與觸控座標轉換,以及物件選取。文件介紹多種相機控制類別,例如 OrbitControls、FlyControls、FirstPersonControls、PointerLockControls、TrackballControls、MapControls、TransformControls 與 DragControls。內容也涵蓋鍵盤輸入、世界座標與螢幕座標互轉、射線與平面求交、互動管理類別,以及效能最佳化建議。
適用情境
適用於在 Three.js 中實作互動式 3D 體驗時,例如點擊偵測、物件挑選、懸停效果、相機導覽,或可拖曳與可變換的物件。也適合在加入觸控支援或在螢幕座標與世界座標之間轉換時使用。
執行需求
需要 Three.js 專案環境;此技能僅為說明與程式碼範例,不附帶指令碼。除安裝 Three.js 外,不需要憑證或網路存取。

Three.js Interaction

Quick Start

javascript
import * as THREE from "three";import { OrbitControls } from "three/addons/controls/OrbitControls.js";
// Camera controlsconst controls = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true;
// Raycasting for click detectionconst raycaster = new THREE.Raycaster();const mouse = new THREE.Vector2();
function onClick(event) {  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);  const intersects = raycaster.intersectObjects(scene.children);
  if (intersects.length > 0) {    console.log("Clicked:", intersects[0].object);  }}
window.addEventListener("click", onClick);

Raycaster

Basic Raycasting

javascript
const raycaster = new THREE.Raycaster();
// From camera (mouse picking)raycaster.setFromCamera(mousePosition, camera);
// From any origin and directionraycaster.set(origin, direction); // origin: Vector3, direction: normalized Vector3
// Get intersectionsconst intersects = raycaster.intersectObjects(objects, recursive);
// intersects array contains:// {//   distance: number,          // Distance from ray origin//   point: Vector3,            // Intersection point in world coords//   face: Face3,               // Intersected face//   faceIndex: number,         // Face index//   object: Object3D,          // Intersected object//   uv: Vector2,               // UV coordinates at intersection//   uv1: Vector2,              // Second UV channel//   normal: Vector3,           // Interpolated face normal//   instanceId: number         // For InstancedMesh// }

Mouse Position Conversion

javascript
const mouse = new THREE.Vector2();
function updateMouse(event) {  // For full window  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;}
// For specific canvas elementfunction updateMouseCanvas(event, canvas) {  const rect = canvas.getBoundingClientRect();  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;  mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;}

Touch Support

javascript
function onTouchStart(event) {  event.preventDefault();
  if (event.touches.length === 1) {    const touch = event.touches[0];    mouse.x = (touch.clientX / window.innerWidth) * 2 - 1;    mouse.y = -(touch.clientY / window.innerHeight) * 2 + 1;
    raycaster.setFromCamera(mouse, camera);    const intersects = raycaster.intersectObjects(clickableObjects);
    if (intersects.length > 0) {      handleSelection(intersects[0]);    }  }}
renderer.domElement.addEventListener("touchstart", onTouchStart);

Raycaster Options

javascript
const raycaster = new THREE.Raycaster();
// Near/far clipping (default: 0, Infinity)raycaster.near = 0;raycaster.far = 100;
// Line/Points precisionraycaster.params.Line.threshold = 0.1;raycaster.params.Points.threshold = 0.1;
// Layers (only intersect objects on specific layers)raycaster.layers.set(1);

Efficient Raycasting

javascript
// Only check specific objectsconst clickables = [mesh1, mesh2, mesh3];const intersects = raycaster.intersectObjects(clickables, false);
// Use layers for filteringmesh1.layers.set(1); // Clickable layerraycaster.layers.set(1);
// Throttle raycast for hover effectslet lastRaycast = 0;function onMouseMove(event) {  const now = Date.now();  if (now - lastRaycast < 50) return; // 20fps max  lastRaycast = now;
  // Raycast here}

Camera Controls

OrbitControls

javascript
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
const controls = new OrbitControls(camera, renderer.domElement);
// Damping (smooth movement)controls.enableDamping = true;controls.dampingFactor = 0.05;
// Rotation limitscontrols.minPolarAngle = 0; // Topcontrols.maxPolarAngle = Math.PI / 2; // Horizoncontrols.minAzimuthAngle = -Math.PI / 4; // Leftcontrols.maxAzimuthAngle = Math.PI / 4; // Right
// Zoom limitscontrols.minDistance = 2;controls.maxDistance = 50;
// Enable/disable featurescontrols.enableRotate = true;controls.enableZoom = true;controls.enablePan = true;
// Auto-rotatecontrols.autoRotate = true;controls.autoRotateSpeed = 2.0;
// Target (orbit point)controls.target.set(0, 1, 0);
// Update in animation loopfunction animate() {  controls.update(); // Required for damping and auto-rotate  renderer.render(scene, camera);}

FlyControls

javascript
import { FlyControls } from "three/addons/controls/FlyControls.js";
const controls = new FlyControls(camera, renderer.domElement);controls.movementSpeed = 10;controls.rollSpeed = Math.PI / 24;controls.dragToLook = true;
// Update with deltafunction animate() {  controls.update(clock.getDelta());  renderer.render(scene, camera);}

FirstPersonControls

javascript
import { FirstPersonControls } from "three/addons/controls/FirstPersonControls.js";
const controls = new FirstPersonControls(camera, renderer.domElement);controls.movementSpeed = 10;controls.lookSpeed = 0.1;controls.lookVertical = true;controls.constrainVertical = true;controls.verticalMin = Math.PI / 4;controls.verticalMax = (Math.PI * 3) / 4;
function animate() {  controls.update(clock.getDelta());}

PointerLockControls

javascript
import { PointerLockControls } from "three/addons/controls/PointerLockControls.js";
const controls = new PointerLockControls(camera, document.body);
// Lock pointer on clickdocument.addEventListener("click", () => {  controls.lock();});
controls.addEventListener("lock", () => {  console.log("Pointer locked");});
controls.addEventListener("unlock", () => {  console.log("Pointer unlocked");});
// Movementconst velocity = new THREE.Vector3();const direction = new THREE.Vector3();const moveForward = false;const moveBackward = false;
document.addEventListener("keydown", (event) => {  switch (event.code) {    case "KeyW":      moveForward = true;      break;    case "KeyS":      moveBackward = true;      break;  }});
function animate() {  if (controls.isLocked) {    direction.z = Number(moveForward) - Number(moveBackward);    direction.normalize();
    velocity.z -= direction.z * 0.1;    velocity.z *= 0.9; // Friction
    controls.moveForward(-velocity.z);  }}

TrackballControls

javascript
import { TrackballControls } from "three/addons/controls/TrackballControls.js";
const controls = new TrackballControls(camera, renderer.domElement);controls.rotateSpeed = 2.0;controls.zoomSpeed = 1.2;controls.panSpeed = 0.8;controls.staticMoving = true;
function animate() {  controls.update();}

MapControls

javascript
import { MapControls } from "three/addons/controls/MapControls.js";
const controls = new MapControls(camera, renderer.domElement);controls.enableDamping = true;controls.dampingFactor = 0.05;controls.screenSpacePanning = false;controls.maxPolarAngle = Math.PI / 2;

TransformControls

Gizmo for moving/rotating/scaling objects.

javascript
import { TransformControls } from "three/addons/controls/TransformControls.js";
const transformControls = new TransformControls(camera, renderer.domElement);scene.add(transformControls);
// Attach to objecttransformControls.attach(selectedMesh);
// Switch modestransformControls.setMode("translate"); // 'translate', 'rotate', 'scale'
// Change spacetransformControls.setSpace("local"); // 'local', 'world'
// SizetransformControls.setSize(1);
// EventstransformControls.addEventListener("dragging-changed", (event) => {  // Disable orbit controls while dragging  orbitControls.enabled = !event.value;});
transformControls.addEventListener("change", () => {  renderer.render(scene, camera);});
// Keyboard shortcutswindow.addEventListener("keydown", (event) => {  switch (event.key) {    case "g":      transformControls.setMode("translate");      break;    case "r":      transformControls.setMode("rotate");      break;    case "s":      transformControls.setMode("scale");      break;    case "Escape":      transformControls.detach();      break;  }});

DragControls

Drag objects directly.

javascript
import { DragControls } from "three/addons/controls/DragControls.js";
const draggableObjects = [mesh1, mesh2, mesh3];const dragControls = new DragControls(  draggableObjects,  camera,  renderer.domElement,);
dragControls.addEventListener("dragstart", (event) => {  orbitControls.enabled = false;  event.object.material.emissive.set(0xaaaaaa);});
dragControls.addEventListener("drag", (event) => {  // Constrain to ground plane  event.object.position.y = 0;});
dragControls.addEventListener("dragend", (event) => {  orbitControls.enabled = true;  event.object.material.emissive.set(0x000000);});

Selection System

Click to Select

javascript
const raycaster = new THREE.Raycaster();const mouse = new THREE.Vector2();let selectedObject = null;
function onMouseDown(event) {  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);  const intersects = raycaster.intersectObjects(selectableObjects);
  // Deselect previous  if (selectedObject) {    selectedObject.material.emissive.set(0x000000);  }
  // Select new  if (intersects.length > 0) {    selectedObject = intersects[0].object;    selectedObject.material.emissive.set(0x444444);  } else {    selectedObject = null;  }}

Box Selection

javascript
import { SelectionBox } from "three/addons/interactive/SelectionBox.js";import { SelectionHelper } from "three/addons/interactive/SelectionHelper.js";
const selectionBox = new SelectionBox(camera, scene);const selectionHelper = new SelectionHelper(renderer, "selectBox"); // CSS class
document.addEventListener("pointerdown", (event) => {  selectionBox.startPoint.set(    (event.clientX / window.innerWidth) * 2 - 1,    -(event.clientY / window.innerHeight) * 2 + 1,    0.5,  );});
document.addEventListener("pointermove", (event) => {  if (selectionHelper.isDown) {    selectionBox.endPoint.set(      (event.clientX / window.innerWidth) * 2 - 1,      -(event.clientY / window.innerHeight) * 2 + 1,      0.5,    );  }});
document.addEventListener("pointerup", (event) => {  selectionBox.endPoint.set(    (event.clientX / window.innerWidth) * 2 - 1,    -(event.clientY / window.innerHeight) * 2 + 1,    0.5,  );
  const selected = selectionBox.select();  console.log("Selected objects:", selected);});

Hover Effects

javascript
const raycaster = new THREE.Raycaster();const mouse = new THREE.Vector2();let hoveredObject = null;
function onMouseMove(event) {  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);  const intersects = raycaster.intersectObjects(hoverableObjects);
  // Reset previous hover  if (hoveredObject) {    hoveredObject.material.color.set(hoveredObject.userData.originalColor);    document.body.style.cursor = "default";  }
  // Apply new hover  if (intersects.length > 0) {    hoveredObject = intersects[0].object;    if (!hoveredObject.userData.originalColor) {      hoveredObject.userData.originalColor =        hoveredObject.material.color.getHex();    }    hoveredObject.material.color.set(0xff6600);    document.body.style.cursor = "pointer";  } else {    hoveredObject = null;  }}
window.addEventListener("mousemove", onMouseMove);

Keyboard Input

javascript
const keys = {};
document.addEventListener("keydown", (event) => {  keys[event.code] = true;});
document.addEventListener("keyup", (event) => {  keys[event.code] = false;});
function update() {  const speed = 0.1;
  if (keys["KeyW"]) player.position.z -= speed;  if (keys["KeyS"]) player.position.z += speed;  if (keys["KeyA"]) player.position.x -= speed;  if (keys["KeyD"]) player.position.x += speed;  if (keys["Space"]) player.position.y += speed;  if (keys["ShiftLeft"]) player.position.y -= speed;}

World-Screen Coordinate Conversion

World to Screen

javascript
function worldToScreen(position, camera) {  const vector = position.clone();  vector.project(camera);
  return {    x: ((vector.x + 1) / 2) * window.innerWidth,    y: (-(vector.y - 1) / 2) * window.innerHeight,  };}
// Position HTML element over 3D objectconst screenPos = worldToScreen(mesh.position, camera);element.style.left = screenPos.x + "px";element.style.top = screenPos.y + "px";

Screen to World

javascript
function screenToWorld(screenX, screenY, camera, targetZ = 0) {  const vector = new THREE.Vector3(    (screenX / window.innerWidth) * 2 - 1,    -(screenY / window.innerHeight) * 2 + 1,    0.5,  );
  vector.unproject(camera);
  const dir = vector.sub(camera.position).normalize();  const distance = (targetZ - camera.position.z) / dir.z;
  return camera.position.clone().add(dir.multiplyScalar(distance));}

Ray-Plane Intersection

javascript
function getRayPlaneIntersection(mouse, camera, plane) {  const raycaster = new THREE.Raycaster();  raycaster.setFromCamera(mouse, camera);
  const intersection = new THREE.Vector3();  raycaster.ray.intersectPlane(plane, intersection);
  return intersection;}
// Ground planeconst groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);const worldPos = getRayPlaneIntersection(mouse, camera, groundPlane);

Event Handling Best Practices

javascript
class InteractionManager {  constructor(camera, renderer, scene) {    this.camera = camera;    this.renderer = renderer;    this.scene = scene;    this.raycaster = new THREE.Raycaster();    this.mouse = new THREE.Vector2();    this.clickables = [];
    this.bindEvents();  }
  bindEvents() {    const canvas = this.renderer.domElement;
    canvas.addEventListener("click", (e) => this.onClick(e));    canvas.addEventListener("mousemove", (e) => this.onMouseMove(e));    canvas.addEventListener("touchstart", (e) => this.onTouchStart(e));  }
  updateMouse(event) {    const rect = this.renderer.domElement.getBoundingClientRect();    this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;    this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;  }
  getIntersects() {    this.raycaster.setFromCamera(this.mouse, this.camera);    return this.raycaster.intersectObjects(this.clickables, true);  }
  onClick(event) {    this.updateMouse(event);    const intersects = this.getIntersects();
    if (intersects.length > 0) {      const object = intersects[0].object;      if (object.userData.onClick) {        object.userData.onClick(intersects[0]);      }    }  }
  addClickable(object, callback) {    this.clickables.push(object);    object.userData.onClick = callback;  }
  dispose() {    // Remove event listeners  }}
// Usageconst interaction = new InteractionManager(camera, renderer, scene);interaction.addClickable(mesh, (intersect) => {  console.log("Clicked at:", intersect.point);});

Performance Tips

  1. Limit raycasts: Throttle mousemove handlers
  2. Use layers: Filter raycast targets
  3. Simple collision meshes: Use invisible simpler geometry for raycasting
  4. Disable controls when not needed: controls.enabled = false
  5. Batch updates: Group interaction checks
javascript
// Use simpler geometry for raycastingconst complexMesh = loadedModel;const collisionMesh = new THREE.Mesh(  new THREE.BoxGeometry(1, 1, 1),  new THREE.MeshBasicMaterial({ visible: false }),);collisionMesh.userData.target = complexMesh;clickables.push(collisionMesh);

See Also

  • threejs-fundamentals - Camera and scene setup
  • threejs-animation - Animating interactions
  • threejs-shaders - Visual feedback effects

來源與署名

來源:CloudAI-X/threejs-skills位於skills/threejs-interaction提交b1c6230

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架