Step-by-Step Implementation
1. Set Up the Scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.z = 5;
2. Define the Dropdown Texture
let dropdownOpen = false;
let selectedOption = 'Option 1';
const options = ['Option 1', 'Option 2', 'Option 3'];
function createDropdownTexture() {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#333';
ctx.fillRect(20, 20, 216, 40); // closed dropdown or selected option
ctx.fillStyle = '#fff';
ctx.font = '20px sans-serif';
ctx.fillText(selectedOption, 30, 48);
if (dropdownOpen) {
for (let i = 0; i < options.length; i++) {
ctx.fillStyle = '#ccc';
ctx.fillRect(20, 70 + i * 40, 216, 40);
ctx.fillStyle = '#000';
ctx.fillText(options[i], 30, 98 + i * 40);
}
}
return new THREE.CanvasTexture(canvas);
}
3. Create and Add the Plane
let dropdownTexture = createDropdownTexture();
const material = new THREE.MeshBasicMaterial({ map: dropdownTexture, transparent: true });
const geometry = new THREE.PlaneGeometry(2.5, 2.5);
const dropdownPlane = new THREE.Mesh(geometry, material);
scene.add(dropdownPlane);
4. Handle Raycasting and Interaction
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.intersectObject(dropdownPlane);
if (intersects.length > 0) {
const uv = intersects[0].uv;
const x = uv.x * 256;
const y = (1 - uv.y) * 256;
if (!dropdownOpen && x >= 20 && x <= 236 && y >= 20 && y <= 60) {
dropdownOpen = true;
} else if (dropdownOpen && y >= 70 && y <= 190) {
const index = Math.floor((y - 70) / 40);
if (options[index]) {
selectedOption = options[index];
dropdownOpen = false;
}
} else {
dropdownOpen = false;
}
updateDropdown();
}
}
function updateDropdown() {
dropdownTexture = createDropdownTexture();
material.map = dropdownTexture;
material.map.needsUpdate = true;
}
window.addEventListener('click', onClick);
5. Render Loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
✅ Result
You now have a 3D dropdown menu inside a plane that:
- Opens and closes when clicked.
- Lets users select an option.
- Renders options and state dynamically within a CanvasTexture.
All this happens inside the 3D scene, with no HTML/CSS elements on top of your WebGL canvas.