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

在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 23:17:42