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

如何基于对象变化依赖创建Hook,监听ThreeJS相机缩放更新UI滑块

问题原因分析
  • 第一个报错 Variable 'camera' is used before being assigned.ts(2454):
    你声明的顶层camera变量仅在组件挂载后执行的useEffect中才会赋值,而useCallback在组件首次渲染初始化时就会读取camera.zoom作为依赖,此时camera还未完成赋值;同时顶层let声明的变量会在组件每次重渲染时重置,之前存储的相机实例会直接丢失。
  • 第二个报错 Cannot assign to 'current' because it is a read-only property.ts(2540):
    你初始化CameraZoom这个ref时只传了null且未指定泛型,TS会自动推断其current属性为只读的null类型,不允许修改值;另外ref的current属性变化不会触发React的响应式更新,既不能作为useCallback的依赖项触发回调更新,也无法驱动UI滑块刷新。
正确实现方案

实现思路:

  1. 相机、渲染器、控制器这类持久化实例统一用useRef存储,避免重渲染丢失
  2. 缩放值用useState存储,天然支持响应式更新UI滑块
  3. 监听控制器变化或者在渲染循环中判断缩放值变动,触发回调更新
  4. 补全TS泛型定义避免类型报错
import { useRef, useEffect, useState, useCallback } from 'react'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' // 如果你用轨道控制器控制缩放可引入

type Props = {
  // 补充你的组件入参定义
}

export const SceneComponent = (props: Props): React.ReactElement => {
  const reactCanvas = useRef<HTMLCanvasElement>(null)
  // 存储相机实例,指定泛型避免类型报错
  const cameraRef = useRef<THREE.PerspectiveCamera | null>(null)
  const rendererRef = useRef<THREE.WebGLRenderer | null>(null)
  const controlsRef = useRef<OrbitControls | null>(null)
  // 缩放值用state存储,驱动UI响应式更新,初始值和相机默认zoom保持一致
  const [cameraZoom, setCameraZoom] = useState(1)

  // 缩放变化回调
  const handleCameraZoomChanged = useCallback((newZoom: number) => {
    console.log('Camera zoom changed:', newZoom)
    // 这里更新你的缩放滑块组件的值即可
  }, [])

  useEffect(() => {
    if (!reactCanvas.current) return
    const canvas = reactCanvas.current
    const width = canvas.clientWidth
    const height = canvas.clientHeight

    const scene = new THREE.Scene()
    // 初始化相机存入ref
    const camera = new THREE.PerspectiveCamera(75, width / height, 1, 10000)
    cameraRef.current = camera

    // 初始化渲染器
    const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })
    renderer.setSize(width, height)
    rendererRef.current = renderer

    // 若使用轨道控制器控制缩放,可直接监听控制器change事件
    const controls = new OrbitControls(camera, renderer.domElement)
    controlsRef.current = controls
    controls.addEventListener('change', () => {
      // 仅当zoom实际变化时更新,避免多余渲染
      if (camera.zoom !== cameraZoom) {
        setCameraZoom(camera.zoom)
        handleCameraZoomChanged(camera.zoom)
      }
    })

    // 渲染循环
    const renderScene = () => {
      requestAnimationFrame(renderScene)
      controls.update()
      renderer.render(scene, camera)
      // 如果不用轨道控制器、是手动修改相机zoom的场景,可在这里判断zoom变化
      // if (camera.zoom !== cameraZoom) {
      //   setCameraZoom(camera.zoom)
      //   handleCameraZoomChanged(camera.zoom)
      // }
    }
    renderScene()

    // 组件卸载清理资源
    return () => {
      controls.removeEventListener('change', () => {})
      renderer.dispose()
    }
  }, [reactCanvas, handleCameraZoomChanged, cameraZoom])

  return (
    <div className='scene-component'>
      <Toast ref={toastReload} />
      <PlayerBtn />
      <SliderSpeed />
      <SliderTimeline />
      {/* 把缩放值传给滑块组件即可同步UI */}
      <SliderZoom value={cameraZoom} />
      <canvas className='canvas-holder' ref={reactCanvas} {...props} />
    </div>
  )
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 05:36:04