You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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");
   });
  }
});

问题根源

  1. 你在目标实体内部嵌套了<a-cursor>,同时给实体本身添加了cursor属性,导致射线检测逻辑混乱,任意区域点击都被判定为触发目标事件。
  2. 同时用了三种方式绑定点击事件:loader内的监听、自定义test组件内的监听,多重绑定导致触发逻辑失控。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 22:08:15