🎯

threejs-interaction

🎯Skill

from toilahuongg/shopify-agents-kit

VibeIndex|
What it does

Enables interactive 3D experiences by handling raycasting, mouse/touch input, and object selection in Three.js scenes.

πŸ“¦

Part of

toilahuongg/shopify-agents-kit(35 items)

threejs-interaction

Installation

npm installInstall npm package
npm install -g shopify-cc-kit
npm installInstall npm package
npm install shopify-cc-kit
πŸ“– Extracted from docs: toilahuongg/shopify-agents-kit
1Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

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.

Overview

# Three.js Interaction

Quick Start

```javascript

import * as THREE from "three";

import { OrbitControls } from "three/addons/controls/OrbitControls.js";

// Camera controls

const controls = new OrbitControls(camera, renderer.domElement);

controls.enableDamping = true;

// Raycasting for click detection

const 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 direction

raycaster.set(origin, direction); // origin: Vector3, direction: normalized Vector3

// Get intersections

const 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 element

function 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 precision

raycaster.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 objects

const clickables = [mesh1, mesh2, mesh3];

const intersects = raycaster.intersectObjects(clickables, false);

// Use layers for filtering

mesh1.layers.set(1); // Clickable layer

raycaster.layers.set(1);

// Throttle raycast for hover effects

let 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 limits

controls.minPolarAngle = 0; // Top

controls.maxPolarAngle = Math.PI / 2; // Horizon

controls.minAzimuthAngle = -Math.PI / 4; // Left

controls.maxAzimuthAngle = Math.PI / 4; // Right

// Zoom limits

controls.minDistance = 2;

controls.maxDistance = 50;

// Enable/disable features

controls.enableRotate = true;

controls.enableZoom = true;

controls.enablePan = true;

// Auto-rotate

controls.autoRotate = true;

controls.autoRotateSpeed = 2.0;

// Target (orbit point)

controls.target.set(0, 1, 0);

// Update in animation loop

function 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 delta

function 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 click

document.addEventListener("click", () => {

controls.lock();

});

controls.addEventListener("lock", () => {

console.log("Pointer locked");

});

controls.addEventListener("unlock", () => {

console.log("Pointer unlocked");

});

// Movement

const 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 object

transformControls.attach(selectedMesh);

// Switch modes

transformControls.setMode("translate"); // 'translate', 'rotate', 'scale'

// Change space

transformControls.setSpace("local"); // 'local', 'world'

// Size

transformControls.setSize(1);

// Events

transformControls.addEventListener("dragging-changed", (event) => {

// Disable orbit controls while dragging

orbitControls.enabled = !event.value;

});

transformControls.addEventListener("change", () => {

renderer.render(scene, camera);

});

// Keyboard shortcuts

window.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 object

const 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 plane

const 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

}

}

// Usage

const 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 raycasting

const 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

More from this repository10

🎯
shopify-polaris-icons🎯Skill

Provides comprehensive guidance and examples for integrating and using Shopify Polaris Icons in commerce applications with accessibility and semantic color tones.

🎯
shopify-polaris-viz🎯Skill

Generates accessible, Shopify Admin-styled data visualizations using Polaris Viz library for React applications.

🎯
shopify-polaris-design🎯Skill

Generates Shopify Polaris-styled React components and design elements for creating consistent and professional e-commerce interfaces.

🎯
threejs-loaders🎯Skill

Loads and manages 3D assets in Three.js, handling GLTF models, textures, and environments with comprehensive loading progress tracking.

🎯
brainstorm🎯Skill

Guides users through creative brainstorming by applying structured techniques like SCAMPER and Design Thinking to generate innovative solutions.

🎯
threejs-animation🎯Skill

Enables precise and flexible object animations in Three.js, supporting keyframe, skeletal, and procedural motion with advanced blending and playback control.

🎯
shopify-webhooks🎯Skill

Handles Shopify webhook verification, processing, and registration for real-time event synchronization with secure HMAC authentication.

🎯
shopify-extensions🎯Skill

Guides developers through building and integrating Shopify Extensions across Admin, Checkout, Theme, and Post-purchase interfaces using latest CLI and APIs.

🎯
design🎯Skill

Generates design-related tasks and recommendations for Shopify store aesthetics, layout, and visual branding using AI-powered analysis.

🎯
algorithmic-art🎯Skill

Generates algorithmic art philosophies and p5.js code sketches using computational aesthetics, seeded randomness, and generative design principles.