React中如何为A-Frame指定实体添加精准点击弹窗事件
解决A-Frame中点击任意位置触发弹窗的问题
问题描述
我是React和A-Frame的新手,正在基于A-Frame开发虚拟电商POC,使用GLB模型展示店铺及产品。我希望为id为samsung-phone的<a-entity>添加点击事件,点击该产品GLB模型时弹出产品详情弹窗。但目前点击场景任意位置都会触发弹窗,请问该如何实现仅点击目标实体才弹窗?
当前代码
App.js
import 'aframe'; import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'; import sky from './stars.jpg'; import mountain from './models/Exterior_baked_with_table.glb'; import newphone from './models/phone.glb'; import mallstore from './models/mallstore.glb'; import Comp from "./Comp.js" import React, { useState,useRef, Component, useEffect } from 'react'; import PopModal from './PopModal'; import * as NEW from 'aframe-react'; import ReactDOM from 'react-dom'; import * as THREE from 'three'; import './newAframe.js' const App = () => { const entityRef=useRef(null); const [popUpModal, setPopUpModal]=useState(false); const loader = new GLTFLoader(); const doSomething = () => { setPopUpModal(!popUpModal); }; loader.load(mountain,(d) =>{ // alert(3) const entity= document.getElementById("mountain"); entity.object3D.add(d.scene); }) loader.load(newphone,(d) =>{ console.log("2"); const phoneEntity= document.getElementById("samsung-phone"); phoneEntity.object3D.add(d.scene); // const phoneEntity1= document.getElementById("samsung-phone1"); // phoneEntity1.object3D.add(d.scene); // Attach a cursor to the entity phoneEntity.setAttribute("cursor", "rayOrigin: mouse; fuseTimeout: 500;"); // Listen for click events phoneEntity.addEventListener("click", () => { doSomething(); }); }) return ( <> <React.Fragment> {popUpModal && <PopModal show={popUpModal} onCloseClick={() => setPopUpModal(false)} message="Are you sure you want to submit?" /> } <a-scene info-message="htmlSrc: #messageText"> <a-assets> <img id="sky" src={sky} alt="sky img"/> </a-assets> <a-sky color='#ffffff' material="src: #sky" rotation="0 0 0"> </a-sky> <div style={{margin:"10px", zIndex:999999}} className="position-relative"> <div className="position-absolute top-0 start-0"> <button className='btn btn-warning btn-sm' ><i className="fa fa-bars"></i>Category</button> </div> <div className="position-absolute top-0 end-0"> <button className='btn btn-warning btn-sm'>Add to cart</button> </div> </div> <a-entity id="mountain" position="0 0 0"></a-entity> <a-entity test id="samsung-phone" ref={entityRef} position="-2.75 1 0.5" scale="1 1 1" raycaster="objects: #samsung-phone"> <a-cursor cursor="rayOrigin: mouse;" target="samsung-phone"></a-cursor> </a-entity> </a-scene> </React.Fragment> </> ); } export default App;
newAframe.js
import { AFrame } from "aframe"; const AFRAME= window.AFRAME; AFRAME.registerComponent('test', { init: function () { const clickme=document.getElementById("samsung-phone"); clickme.addEventListener('click', function (evt) { console.log("hi mamta"); }); } });
问题根源
- 你在目标实体内部嵌套了
<a-cursor>,同时给实体本身添加了cursor属性,导致射线检测逻辑混乱,任意区域点击都被判定为触发目标事件。 - 同时用了三种方式绑定点击事件:loader内的监听、自定义
test组件内的监听,多重绑定导致触发逻辑失控。 raycaster组件配置错误,应该给相机添加而非目标实体,导致射线检测范围不符合预期。
修复方案
步骤1:清理冗余配置与重复事件绑定
- 删除
newAframe.js文件,移除自定义的test组件,避免重复绑定点击事件。 - 移除
samsung-phone实体上的cursor、raycaster属性,以及内部嵌套的<a-cursor>标签。
步骤2:正确配置射线检测
将raycaster和cursor移到相机上,指定仅检测目标实体,确保只有点击到samsung-phone时才触发事件:
<a-camera raycaster="objects: #samsung-phone"> <a-cursor rayOrigin="mouse" fuseTimeout="500"></a-cursor> </a-camera>
步骤3:优化React内的事件绑定逻辑
用React的useRef和useEffect替代直接DOM操作,确保事件只绑定一次,且组件卸载时自动清理监听。
修改后的完整App.js代码
import 'aframe'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; import sky from './stars.jpg'; import mountain from './models/Exterior_baked_with_table.glb'; import newphone from './models/phone.glb'; import React, { useState, useRef, useEffect } from 'react'; import PopModal from './PopModal'; import * as THREE from 'three'; const App = () => { const entityRef = useRef(null); const [popUpModal, setPopUpModal] = useState(false); const loader = new GLTFLoader(); const doSomething = () => { setPopUpModal(!popUpModal); }; useEffect(() => { // 加载山地模型 loader.load(mountain, (d) => { const entity = document.getElementById("mountain"); entity.object3D.add(d.scene); }); // 加载手机模型并绑定点击事件 const loadPhoneModel = () => { loader.load(newphone, (d) => { if (!entityRef.current) return; const phoneEntity = entityRef.current; phoneEntity.object3D.add(d.scene); // 绑定点击事件 phoneEntity.addEventListener("click", doSomething); // 组件卸载时移除监听,避免内存泄漏 return () => { phoneEntity.removeEventListener("click", doSomething); }; }); }; loadPhoneModel(); }, []); return ( <> {popUpModal && ( <PopModal show={popUpModal} onCloseClick={() => setPopUpModal(false)} message="Are you sure you want to submit?" /> )} <a-scene> <a-assets> <img id="sky" src={sky} alt="sky img" /> </a-assets> <a-sky color='#ffffff' material="src: #sky" rotation="0 0 0"></a-sky> <div style={{ margin: "10px", zIndex: 999999 }} className="position-relative"> <div className="position-absolute top-0 start-0"> <button className='btn btn-warning btn-sm'><i className="fa fa-bars"></i>Category</button> </div> <div className="position-absolute top-0 end-0"> <button className='btn btn-warning btn-sm'>Add to cart</button> </div> </div> <a-entity id="mountain" position="0 0 0"></a-entity> <a-entity id="samsung-phone" ref={entityRef} position="-2.75 1 0.5" scale="1 1 1"></a-entity> {/* 相机配置射线检测,仅目标实体可被点击 */} <a-camera raycaster="objects: #samsung-phone"> <a-cursor rayOrigin="mouse" fuseTimeout="500"></a-cursor> </a-camera> </a-scene> </> ); } export default App;
效果说明
修改后,只有点击samsung-phone实体对应的GLB模型时,才会触发弹窗;点击场景其他区域不会有任何反应,完全符合需求。
内容的提问来源于stack exchange,提问作者CoderM
相关产品推荐
相关产品推荐

