【图片效果完整代码位于文章末】
1.基本原理
在Three.js中,为了实现鼠标与3D物体的点击交互,我们主要利用Raycaster类将鼠标在屏幕上的2D坐标转换为3D空间中的射线,然后检测这条射线是否与场景中的3D物体相交。当射线与物体相交时,即视为点击到了该物体。
2.实现步骤
1.创建两个可以被点击的物体
物体的创建基础祥见上一篇文章threejs基础开发应用示例,这里创建了两个方块,我们给他分别命名为“方块1”,“方块2”,并使他们反方向旋转。
function init() {
camera.position.set(0, 0, 5)
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material1 = new THREE.MeshBasicMaterial({
color: 0xff00a2d7,
transparent: true,
opacity: 0.5,
})
const material2 = new THREE.MeshBasicMaterial({
color: 0xffd3e3fd,
transparent: true,
opacity: 0.5,
})
const cube1 = new THREE.Mesh(geometry, material1)
const cube2 = new THREE.Mesh(geometry, material2)
scene.add(cube1, cube2)
cube1.position.set(0, 0, 0)
cube1.name = '方块1'
cube2.position.set(2, 0, 0)
cube2.name = '方块2'
cube1.position.x = -2
controls.update()
function animate() {
requestAnimationFrame(animate)
controls.update()
cube1.rotation.y += 0.01
cube2.rotation.y -= 0.01
renderer.render(scene, camera)
}
animate()
}
2.初始化Raycaster与鼠标坐标
// 创建射线投射器
const raycaster = new THREE.Raycaster()
// 鼠标位置
const mouse = new THREE.Vector2()
3.创建鼠标点击事件监听
我们需要创建一个鼠标点击事件的监听,然后将鼠标坐标归一化,从而设置射线的起点和方向。当射线与物体相交时,即可获取到鼠标点击的物体。通过对获取到的物体材质进行设置,即可改变物体的颜色等属性。
// 鼠标点击事件监听
window.addEventListener('click', mouseClick, false)
function mouseClick(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, true)
if (intersects.length > 0) {
// 选中物体
const selectedObject = intersects[0].object
alert(`点击了${selectedObject.name}`)
// 改变当前被点击物体的颜色
selectedObject.material.color.set(0xff62e258)
}
}
4.物体颜色的恢复
通过用一个变量记录上一个被点击的物体状态,可以在点击其他物体时恢复上一个被点击物体的颜色。具体实现如下
// 记录上一个被点击的对象
let lastSelectedObject = null
function mouseClick(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, true)
if (intersects.length > 0) {
// 如果之前有选中的物体,将其颜色恢复为初始状态
if (lastSelectedObject) {
lastSelectedObject.material.color.set(
lastSelectedObject.initialColor
)
}
// 选中物体
const selectedObject = intersects[0].object
alert(`点击了${selectedObject.name}`)
// 记录当前选中物体的状态
selectedObject.initialColor = selectedObject.material.color.clone()
lastSelectedObject = selectedObject
// 改变当前被点击物体的颜色
selectedObject.material.color.set(0xff62e258)
} else {
// 如果没有新的物体被选中,恢复上一个选中物体的颜色(如果存在的话)
if (lastSelectedObject) {
lastSelectedObject.material.color.set(
lastSelectedObject.initialColor
)
}
}
}
5.最终完整代码
再添加一些其他显示最终实现代码如下:
<template>
<div
style="
font-size: 24px;
color: #ffffff;
text-align: center;
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
"
>
{{ msg }}
</div>
</template>
<script setup>
import * as THREE from 'three'
import { onMounted, ref } from 'vue'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
const msg = ref('')
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
const renderer = new THREE.WebGLRenderer({ antialias: true })
const controls = new OrbitControls(camera, renderer.domElement)
onMounted(() => {
init()
})
function init() {
camera.position.set(0, 0, 5)
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material1 = new THREE.MeshBasicMaterial({
color: 0xff00a2d7,
transparent: true,
opacity: 0.5,
})
const material2 = new THREE.MeshBasicMaterial({
color: 0xffd3e3fd,
transparent: true,
opacity: 0.5,
})
const cube1 = new THREE.Mesh(geometry, material1)
const cube2 = new THREE.Mesh(geometry, material2)
scene.add(cube1, cube2)
cube1.position.set(0, 0, 0)
cube1.name = '方块1'
cube2.position.set(2, 0, 0)
cube2.name = '方块2'
cube1.position.x = -2
controls.update()
function animate() {
requestAnimationFrame(animate)
controls.update()
cube1.rotation.y += 0.01
cube2.rotation.y -= 0.01
renderer.render(scene, camera)
}
animate()
}
// 创建射线投射器
const raycaster = new THREE.Raycaster()
// 鼠标位置
const mouse = new THREE.Vector2()
// 记录上一个被点击的对象
let lastSelectedObject = null
// 鼠标点击事件监听
window.addEventListener('click', mouseClick, false)
function mouseClick(event) {
console.log('点击事件')
// 将鼠标坐标归一化
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, true)
if (intersects.length > 0) {
// 如果之前有选中的物体,将其颜色恢复为初始状态
if (lastSelectedObject) {
lastSelectedObject.material.color.set(
lastSelectedObject.initialColor
)
}
// 选中物体
const selectedObject = intersects[0].object
msg.value = `点击了${selectedObject.name}`
// 记录当前选中物体的状态
selectedObject.initialColor = selectedObject.material.color.clone()
lastSelectedObject = selectedObject
selectedObject.material.color.set(0xff62e258)
} else {
// 如果没有新的物体被选中,恢复上一个选中物体的颜色(如果存在的话)
if (lastSelectedObject) {
lastSelectedObject.material.color.set(
lastSelectedObject.initialColor
)
msg.value = ''
}
}
}
</script>