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

如何让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轴距离,计算视锥体在该距离处的高度,再根据目标屏幕占比反推缩放比例:

  1. 获取当前窗口高度
  2. 计算相机视锥体在模型Z距离处的高度:viewportHeight = 2 * tan(FOV弧度/2) * 距离
  3. 缩放比例 = (目标屏幕高度 / 视锥体高度) / 模型原始高度

代码实现

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 10:05:55