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

如何在react-three/fiber的<Canvas />中添加右键菜单?

React-Three-Fiber中react-use-context-menu右键菜单异常问题解决

在React-Three-Fiber项目中使用react-use-context-menu时,右键菜单弹出异常的核心原因是canvas的WebGL事件机制与普通DOM事件的冲突:canvas会拦截右键事件,且普通DOM元素的事件触发逻辑和canvas的射线检测逻辑不同步,导致菜单弹出时机混乱。

解决步骤及修改代码

1. 调整触发元素与Canvas的绑定逻辑

将右键触发绑定到Canvas的父容器,同时阻止Canvas的默认右键行为,确保事件由我们的菜单统一处理:

// App.js
import { useState, useEffect } from 'react';
import { Canvas } from '@react-three/fiber';
import useContextMenu from "react-use-context-menu";
import Menu from './Menu';

function App() {
  const [
    bindMenu,
    bindMenuItem,
    useContextTrigger,
    { data, coords, setVisible, visible }
  ] = useContextMenu();
  const [bindTrigger] = useContextTrigger({
    collect: () => "Title"
  });
  const [clickedCmd, setClickedCmd] = useState();
  const hideMenu = () => setVisible(false);

  // 点击菜单外部自动关闭
  useEffect(() => {
    const closeOnOutsideClick = (e) => {
      const menu = document.querySelector('.menu');
      if (menu && !menu.contains(e.target)) hideMenu();
    };

    visible && document.addEventListener('click', closeOnOutsideClick);
    return () => document.removeEventListener('click', closeOnOutsideClick);
  }, [visible]);

  return (
    {/* 父容器绑定触发,设置相对定位作为菜单的定位基准 */}
    <div style={{ position: 'relative', width: '100vw', height: '100vh' }} {...bindTrigger}>
      <Canvas 
        onContextMenu={(e) => e.preventDefault()} {/* 禁用Canvas默认右键菜单 */}
        style={{ position: 'absolute', inset: 0 }}
      >
        {/* 你的3D场景内容 */}
        <ambientLight />
        <pointLight position={[10,10,10]} />
        <mesh>
          <boxGeometry args={[1,1,1]} />
          <meshStandardMaterial color="hotpink" />
        </mesh>
      </Canvas>

      {/* 仅在菜单可见时渲染,避免不必要的DOM节点 */}
      {visible && (
        <Menu
          bindMenu={bindMenu}
          data={data}
          bindMenuItem={bindMenuItem}
          coords={coords}
          setClickedCmd={setClickedCmd}
          hideMenu={hideMenu}
        />
      )}
    </div>
  );
}

export default App;

2. 修复菜单定位与交互逻辑

给菜单添加绝对定位和层级控制,确保它在Canvas上层显示,同时优化点击体验:

// Menu.js
import React from "react";
import '../index.css';

const Menu = props => {
  const { bindMenu, bindMenuItem, coords, setClickedCmd, hideMenu } = props;

  const handleClick = (cmd) => () => {
    setClickedCmd(cmd);
    hideMenu();
  };

  // 基于右键点击坐标精确定位菜单
  const menuStyles = {
    position: 'absolute',
    top: `${coords.y}px`,
    left: `${coords.x}px`,
    background: '#fff',
    border: '1px solid #e0e0e0',
    borderRadius: '4px',
    boxShadow: '0 2px 12px rgba(0,0,0,0.1)',
    zIndex: 9999, // 确保菜单在所有3D内容上层
    padding: '6px 0',
    minWidth: '120px'
  };

  const itemStyles = {
    margin: 0,
    padding: '8px 16px',
    cursor: 'pointer',
    fontSize: '14px',
    userSelect: 'none'
  };

  return (
    <div {...bindMenu} className="menu" style={menuStyles}>
       <p {...bindMenuItem} onClick={handleClick("option1")} style={itemStyles}>
        导航选项1
      </p> 
      <p {...bindMenuItem} onClick={handleClick("option2")} style={itemStyles}>
        导航选项2
      </p> 
      <p {...bindMenuItem} onClick={handleClick("option3")} style={itemStyles}>
        导航选项3
      </p> 
      <p {...bindMenuItem} onClick={handleClick("option4")} style={itemStyles}>
        导航选项4
      </p>
      <hr style={{ margin: '6px 0', border: 'none', borderTop: '1px solid #e0e0e0' }} />
      <p style={{ ...itemStyles, fontSize: '12px', color: '#666', padding: '6px 16px' }}>
        点击坐标: {JSON.stringify(coords)}
      </p>
    </div>
  );
};

export default Menu;

关键细节说明

  • 事件隔离:阻止Canvas的默认右键行为,避免浏览器原生菜单和自定义菜单冲突。
  • 定位基准:父容器设置相对定位,确保菜单的绝对定位基于右键点击的全局坐标计算。
  • 外部点击关闭:通过监听全局点击事件,在点击菜单外部时自动关闭,提升交互体验。
  • 层级控制:设置高z-index保证菜单在Canvas上层显示,不会被3D内容遮挡。

内容的提问来源于stack exchange,提问作者SweDaniel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 04:35:53