如何让React Three Fiber画布中GLB对象在浏览器缩放时保持固定大小
React Three Fiber 固定GLB模型屏幕大小解决方案
问题
使用React Three Fiber加载的GLB模型会随浏览器窗口缩放、不同浏览器(如Edge页面可用空间小于Chrome)而改变显示大小。模型作为粘性元素覆盖页面内容,需要实现无论画布尺寸如何,对象始终保持精确的屏幕大小。
相关代码:
<div ref={parentRef} className="max-w-container pointer-events-none sticky bottom-0 left-0 top-0 z-10 mx-auto -mb-[120px] hidden h-screen w-full lg:block [&>*]:!pointer-events-none" > <Canvas flat dpr={[1, 1.5]} gl={{ preserveDrawingBuffer: true, toneMappingExposure: 1, outputEncoding: THREE.sRGBEncoding, }} > <ambientLight ref={light} color={0xffffff} intensity={0.3} /> <Environment files={`${ process.env['NEXT_PUBLIC_ASSET_PREFIX'] ?? '' }/pizzo_pernice_puresky_2k.hdr`} /> <Element ref={meshRef as React.RefObject<Group<Object3DEventMap>>} position={position()} rotation={rotation()} scale={scale()} stateTextureURI={textureURI} onLoad={() => setIsLoaded(true)} /> </Canvas> </div>
解决方案
方法1:透视相机下动态计算缩放比例
如果保留默认透视相机,可通过视口尺寸、相机参数和模型距离的数学关系,动态调整模型缩放,确保屏幕大小固定。
核心逻辑
利用相机视场角(FOV)和模型到相机的Z轴距离,计算视锥体在该距离处的高度,再根据目标屏幕占比反推缩放比例:
- 获取当前窗口高度
- 计算相机视锥体在模型Z距离处的高度:
viewportHeight = 2 * tan(FOV弧度/2) * 距离 - 缩放比例 = (目标屏幕高度 / 视锥体高度) / 模型原始高度
代码实现
import { useWindowSize, useThree } from '@react-three/fiber'; import { useEffect, useState } from 'react'; import * as THREE from 'three'; // 在组件内部 const [fixedScale, setFixedScale] = useState(1); const { camera } = useThree(); const { height: windowHeight } = useWindowSize(); const [modelOriginalHeight, setModelOriginalHeight] = useState(1); const distanceToCamera = 10; // 替换为模型实际Z轴位置值 const targetScreenRatio = 0.2; // 模型占屏幕高度的比例(如20%) // 加载后获取模型原始尺寸 useEffect(() => { if (!meshRef.current) return; const boundingBox = new THREE.Box3().setFromObject(meshRef.current); const size = boundingBox.getSize(new THREE.Vector3()); setModelOriginalHeight(size.y); }, []); // 窗口尺寸变化时重新计算缩放 useEffect(() => { if (!camera || modelOriginalHeight === 1) return; const targetHeight = windowHeight * targetScreenRatio; const fovRadians = (camera.fov * Math.PI) / 180; const viewportHeight = 2 * Math.tan(fovRadians / 2) * distanceToCamera; const scale = (targetHeight / viewportHeight) / modelOriginalHeight; setFixedScale(scale); }, [windowHeight, camera, modelOriginalHeight, distanceToCamera, targetScreenRatio]); // 将fixedScale传给Element组件 <Element ref={meshRef} position={position()} rotation={rotation()} scale={fixedScale} stateTextureURI={textureURI} onLoad={() => setIsLoaded(true)} />
方法2:使用Drei正交相机(推荐)
正交相机的特性是物体大小与距离、视口尺寸无关,天生适合固定屏幕大小的场景。
代码实现
import { OrthographicCamera } from '@react-three/drei'; import { useWindowSize } from '@react-three/fiber'; // 在Canvas内部替换默认相机 const { width, height } = useWindowSize(); const aspectRatio = width / height; const cameraSize = 5; // 调整此值控制模型大小:值越小,模型屏幕占比越大 <Canvas flat dpr={[1, 1.5]} gl={{ preserveDrawingBuffer: true, toneMappingExposure: 1, outputEncoding: THREE.sRGBEncoding, }} > {/* 替换默认透视相机为正交相机 */} <OrthographicCamera makeDefault zoom={1} left={-cameraSize * aspectRatio} right={cameraSize * aspectRatio} top={cameraSize} bottom={-cameraSize} near={0.1} far={1000} /> <ambientLight ref={light} color={0xffffff} intensity={0.3} /> <Environment files={`${ process.env['NEXT_PUBLIC_ASSET_PREFIX'] ?? '' }/pizzo_pernice_puresky_2k.hdr`} /> <Element ref={meshRef} position={position()} rotation={rotation()} scale={scale()} // 此处scale可固定,正交相机下大小不受视口影响 stateTextureURI={textureURI} onLoad={() => setIsLoaded(true)} /> </Canvas>
注意点
- 正交相机下,模型的
position.z仅影响渲染层级,不改变屏幕大小 - 调整
cameraSize或模型的scale即可修改模型的屏幕显示大小
内容的提问来源于stack exchange,提问作者MarioVde
相关产品推荐
相关产品推荐

