React-Three-Fiber实现容器尺寸变化时动态更新Canvas尺寸
问题:React Three Canvas 在CSS动画容器中尺寸更新延迟,无法实时响应
我正在开发一个应用,将react-three-fiber的Canvas嵌套在带有CSS动画的div中。目前遇到的问题是:Canvas的尺寸只有在过渡或动画完成后才会更新,我希望它能像监听窗口resize事件那样实时响应容器尺寸变化。

代码示例:
<div id={ s.UserView }> <Canvas shadows={ false } orthographic camera={{ position: [ 0, 0, 200 ] }} ref={ SceneRef } dpr={ DPR } gl={{ outputColorSpace: THREE.SRGBColorSpace, toneMapping: THREE.NoToneMapping }} > <MapCam targetRef={ containerRef } HUDRef={ HUDRef } sceneFrame={ data.frame } sceneGrids={ data.grids }/> <SceneRoot setCursor={ setCursor } mapFilters={ mapFilters } cells={ data.grids } cellsIndex={ cellsIndex }/> </Canvas> <HUD passRef={ HUDRef }/> </div>
解决方案
核心思路
react-three-fiber的Canvas默认只监听窗口resize事件,不会追踪容器在CSS动画/过渡中的实时尺寸变化。我们需要手动监听容器尺寸,并强制Canvas同步更新。
方案1:用ResizeObserver实时监听容器尺寸
ResizeObserver可以精准捕捉元素尺寸的每一步变化,包括动画过程中的动态调整。
步骤:
- 确保包裹Canvas的div已绑定ref(比如代码里的
containerRef) - 组件挂载后创建ResizeObserver实例,监听容器
- 尺寸变化时,通过Canvas的ref获取renderer和相机,手动更新尺寸与投影矩阵
代码示例:
import { useEffect, useRef } from 'react'; import { Canvas } from '@react-three/fiber'; import * as THREE from 'three'; function YourComponent() { const containerRef = useRef(null); const SceneRef = useRef(null); useEffect(() => { const observer = new ResizeObserver(() => { if (!containerRef.current || !SceneRef.current) return; const { width, height } = containerRef.current.getBoundingClientRect(); const renderer = SceneRef.current.gl; const camera = SceneRef.current.camera; // 更新渲染器尺寸 renderer.setSize(width, height); // 针对正交相机更新投影矩阵(你的场景用的是正交相机) if (camera.isOrthographicCamera) { camera.left = -width / 2; camera.right = width / 2; camera.top = height / 2; camera.bottom = -height / 2; camera.updateProjectionMatrix(); } }); observer.observe(containerRef.current); return () => observer.disconnect(); }, []); return ( <div ref={containerRef} id={s.UserView}> <Canvas shadows={false} orthographic camera={{ position: [0, 0, 200] }} ref={SceneRef} dpr={DPR} gl={{ outputColorSpace: THREE.SRGBColorSpace, toneMapping: THREE.NoToneMapping }} > <MapCam targetRef={containerRef} HUDRef={HUDRef} sceneFrame={data.frame} sceneGrids={data.grids}/> <SceneRoot setCursor={setCursor} mapFilters={mapFilters} cells={data.grids} cellsIndex={cellsIndex}/> </Canvas> <HUD passRef={HUDRef}/> </div> ); }
方案2:结合useFrame钩子确保实时同步
如果ResizeObserver效果不够理想,可以用useFrame在每一帧检查容器尺寸,确保Canvas完全同步:
import { useFrame } from '@react-three/fiber'; // 在组件内部添加 useFrame(() => { if (!containerRef.current || !SceneRef.current) return; const { width, height } = containerRef.current.getBoundingClientRect(); const renderer = SceneRef.current.gl; const camera = SceneRef.current.camera; // 仅在尺寸真的变化时更新,避免不必要的性能消耗 if (renderer.domElement.width !== width || renderer.domElement.height !== height) { renderer.setSize(width, height); if (camera.isOrthographicCamera) { camera.left = -width / 2; camera.right = width / 2; camera.top = height / 2; camera.bottom = -height / 2; camera.updateProjectionMatrix(); } } });
注意事项
- 必须确保
containerRef正确绑定到包裹Canvas的div上 - 正交相机和透视相机的投影矩阵更新逻辑不同,根据你的场景选择(你用的是正交相机,重点处理该逻辑即可)
- 使用
useFrame时添加尺寸判断,能减少不必要的重绘,优化性能
内容的提问来源于stack exchange,提问作者Joseph Ghaida
相关产品推荐
相关产品推荐

