在Android应用中使用@react-three/drei组件时出现渲染错误求助
解决React Native中@react-three/drei组件依赖document的报错问题
问题核心
你猜测的完全正确:@react-three/drei的多数组件(如OrbitControls、Stats、CameraControls)是为Web浏览器环境设计的,它们直接依赖document、window等浏览器专属API。而React Native(包括Expo)运行在原生环境中,不存在这些对象,因此会抛出ReferenceError: Property 'document' doesn't exist。
@react-three/fiber能正常运行,是因为它针对React Native做了适配(通过expo-gl提供WebGL上下文),但drei的大部分组件没有做跨环境兼容。
解决方案
1. 替换相机控制组件:用React Native原生触摸事件实现
放弃drei的OrbitControls,基于React Native的PanResponder实现自定义相机控制,适配移动端触摸操作:
import { StyleSheet, Text, View, SafeAreaView, PanResponder } from 'react-native'; import React, { useRef, useState } from 'react'; import { Canvas, useThree, useFrame } from "@react-three/fiber"; function CustomCameraControls() { const { camera } = useThree(); const [isPanning, setIsPanning] = useState(false); const [lastTouch, setLastTouch] = useState({ x: 0, y: 0 }); const panResponder = useRef( PanResponder.create({ onStartShouldSetPanResponder: () => true, onPanResponderGrant: (_, gestureState) => { setIsPanning(true); setLastTouch({ x: gestureState.x0, y: gestureState.y0 }); }, onPanResponderMove: (_, gestureState) => { if (!isPanning) return; const deltaX = gestureState.x - lastTouch.x; const deltaY = gestureState.y - lastTouch.y; // 调整相机旋转角度,可根据需求修改灵敏度 camera.rotation.y += deltaX * 0.005; camera.rotation.x += deltaY * 0.005; // 限制上下旋转角度,避免相机翻转 camera.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, camera.rotation.x)); setLastTouch({ x: gestureState.x, y: gestureState.y }); }, onPanResponderRelease: () => setIsPanning(false), }) ).current; // 将触摸事件绑定到Canvas容器 React.useEffect(() => { const canvasElement = document.querySelector('canvas'); if (canvasElement) { panResponder.panHandlers.onStart(canvasElement); } }, []); return null; } export default function App() { return ( <SafeAreaView style={{flex:1, backgroundColor:"#ffffff"}}> <Canvas> <color attach="background" args={["#FAD6A5"]} /> <CustomCameraControls /> {/* 测试用立方体 */} <mesh rotation={[0.5, 0.5, 0]}> <boxGeometry args={[1,1,1]} /> <meshStandardMaterial color="coral" /> </mesh> <ambientLight intensity={0.5} /> <pointLight position={[10, 10, 10]} /> </Canvas> </SafeAreaView> ); }
2. 替代Stats组件:自定义帧率监控
Stats组件依赖浏览器DOM元素,无法在RN环境运行,可通过useFrame实现简易帧率统计:
function FPSMonitor() { const [fps, setFps] = useState(0); const lastTime = useRef(Date.now()); const frameCount = useRef(0); useFrame(() => { const now = Date.now(); frameCount.current += 1; if (now - lastTime.current >= 1000) { setFps(frameCount.current); frameCount.current = 0; lastTime.current = now; } }); return ( <Text style={{ position: 'absolute', top: 20, left: 20, backgroundColor: 'rgba(0,0,0,0.5)', color: 'white', padding: 5, fontSize: 12 }}> FPS: {fps} </Text> ); } // 在App中使用: <SafeAreaView style={{flex:1}}> <FPSMonitor /> <Canvas>...</Canvas> </SafeAreaView>
3. 选择性使用drei组件
少数drei组件(如基础几何、材质封装)不依赖浏览器API,可以直接使用。如果必须用到某个drei功能,可手动修改组件源码:
- 通过条件判断区分环境,跳过浏览器API相关逻辑:
const isBrowser = typeof document !== 'undefined'; if (isBrowser) { // 浏览器环境专属代码 } else { // React Native环境替代逻辑 }
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

