本文详解如何使用three.js和raycaster为多个3d模型(如城市中的不同建筑)添加鼠标悬停提示与点击响应功能,涵盖坐标转换、射线检测、事件模拟及模态框触发逻辑。
本文详解如何使用three.js和raycaster为多个3d模型(如城市中的不同建筑)添加鼠标悬停提示与点击响应功能,涵盖坐标转换、射线检测、事件模拟及模态框触发逻辑。
在Three.js中实现3D模型的交互性(如悬停高亮、信息提示、点击弹窗)完全可行,核心依赖于 THREE.Raycaster —— 它通过从相机向屏幕坐标发射一条虚拟射线,检测该射线是否与场景中的指定对象相交,从而精准定位用户操作的目标模型。
✅ 基础准备:统一管理可交互模型
首先,将4栋建筑模型(假设已加载为 THREE.Mesh 实例)存入数组,便于统一检测:
const buildings = [building1, building2, building3, building4]; // 确保每个模型都设置了唯一标识(推荐),便于后续逻辑区分: building1.userData.id = 'office-tower'; building2.userData.id = 'shopping-mall'; building3.userData.id = 'residential-block'; building4.userData.id = 'city-hall';
⚠️ 注意:务必为每个模型设置 userData 或其他自定义属性(如 name),避免直接用 === 比较对象引用(易因重渲染或克隆失效)。
✅ 鼠标坐标映射与射线投射
在渲染循环(requestAnimationFrame)中实时更新射线起点与方向:
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 监听鼠标移动,将屏幕像素坐标归一化到 [-1, 1] 区间
window.addEventListener('mousemove', (event) => {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
});
// 渲染循环内(如 animate() 函数中)
function render() {
raycaster.setFromCamera(mouse, camera); // 关键:从相机位置沿鼠标方向发射射线
const intersects = raycaster.intersectObjects(buildings);
// 模拟 mouseenter / mouseleave
if (intersects.length > 0) {
const target = intersects[0].object;
if (!currentHoverTarget) {
// 首次悬停:显示浮动文本(例如使用 DOM 元素绝对定位)
showTooltip(target.userData.id, intersects[0].point);
currentHoverTarget = target;
}
} else {
if (currentHoverTarget) {
hideTooltip();
currentHoverTarget = null;
}
}
}✅ 实现悬停文本提示(DOM 方式示例)
创建一个轻量级 <div> 作为 tooltip,并动态定位在模型世界坐标投影后的屏幕位置:
const tooltip = document.createElement('div');
tooltip.className = 'tooltip';
tooltip.style.cssText = 'position: absolute; pointer-events: none; background: rgba(0,0,0,0.8); color: white; padding: 8px 12px; border-radius: 4px; font-size: 14px; z-index: 10;';
document.body.appendChild(tooltip);
function showTooltip(id, worldPosition) {
const screenPosition = worldPosition.clone().project(camera);
const x = (screenPosition.x * 0.5 + 0.5) * window.innerWidth;
const y = (-screenPosition.y * 0.5 + 0.5) * window.innerHeight;
tooltip.textContent = `? ${id.replace('-', ' ')}`;
tooltip.style.left = `${x + 15}px`;
tooltip.style.top = `${y - 25}px`;
tooltip.style.display = 'block';
}
function hideTooltip() {
tooltip.style.display = 'none';
}✅ 绑定点击事件:打开对应模态框
监听全局 click 事件,结合当前悬停目标触发业务逻辑:
let currentHoverTarget = null;
window.addEventListener('click', () => {
if (!currentHoverTarget) return;
// 根据模型 ID 打开专属模态框(此处以 Bootstrap Modal 为例)
switch (currentHoverTarget.userData.id) {
case 'office-tower':
$('#buildingModal').find('.modal-title').text('金融大厦');
$('#buildingModal').find('.modal-body').html('<p>42层甲级写字楼,入驻企业超200家。</p>');
$('#buildingModal').modal('show');
break;
case 'shopping-mall':
$('#buildingModal').find('.modal-title').text('星光购物中心');
$('#buildingModal').find('.modal-body').html('<p>集购物、餐饮、娱乐于一体的地标综合体。</p>');
$('#buildingModal').modal('show');
break;
// ... 其他建筑同理
}
});✅ 性能与健壮性建议
- 性能优化:若模型数量较多,可对 intersectObjects() 的检测列表做动态过滤(如仅检测可见/启用状态的模型);
- 抗抖动处理:为避免频繁触发 mouseenter/leave,可添加简单时间阈值(如 Date.now() - lastHoverTime > 50);
- 移动端适配:需补充 touchstart/touchmove 事件,并将 event.touches[0] 替代 event.clientX/Y;
- 无障碍支持:为模型添加 aria-label,并确保模态框支持键盘关闭(ESC)与焦点管理。
通过以上结构化实现,你不仅能精准响应每栋建筑的交互行为,还能轻松扩展至数十个模型——Raycaster 是 Three.js 中实现真实3D UI交互的基石工具,掌握它,即掌握了通往沉浸式Web体验的关键钥匙。


















