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

滚动时3D模型频繁刷新问题求助(React+Three.js)

解决滚动时React Three Fiber模型频繁刷新问题

问题分析

你的代码存在几个导致3D模型频繁刷新的核心问题:

  1. 重复且低效的滚动监听:两个useEffect都绑定了scroll事件,第二个监听里的循环会在每次滚动时多次修改DOM样式,触发不必要的重绘/重排,进而导致Canvas频繁刷新。
  2. 相机位置设置逻辑不当:在Model组件里直接用useThree设置相机位置,这个函数会在每帧渲染时执行,干扰正常渲染流程。
  3. 父元素样式更新触发Canvas重绘:通过修改transform移动容器时,浏览器可能因元素重排强制Canvas重新渲染。

修复方案

1. 优化滚动监听逻辑

合并滚动事件处理,移除无效循环,用requestAnimationFrame优化动画流畅度,避免频繁修改DOM:

const [scrollY, setScrollY] = React.useState(0);
const fridgeRef = useRef(null);

useEffect(() => {
  const handleScroll = () => {
    setScrollY(window.scrollY);
  };

  const updateFridgePosition = () => {
    if (fridgeRef.current) {
      // 直接用scrollY设置位移,无需循环
      fridgeRef.current.style.transform = `translateX(${-scrollY}px)`;
    }
    requestAnimationFrame(updateFridgePosition);
  };

  handleScroll();
  window.addEventListener("scroll", handleScroll);
  const animationId = requestAnimationFrame(updateFridgePosition);

  return () => {
    window.removeEventListener("scroll", handleScroll);
    cancelAnimationFrame(animationId);
  };
}, [scrollY]);

2. 调整Canvas渲染配置

给Canvas添加frameloop="demand"属性,让渲染只在必要时触发(比如相机/模型变化、用户交互),而非持续渲染:

<Canvas frameloop="demand">
  <Suspense fallback={null}>
    <Stage>
      <Model />
      <OrbitControls enableZoom={false} />
    </Stage>
  </Suspense>
</Canvas>

如果需要保持交互流畅,可改用frameloop="always"并结合useFrame控制渲染时机,或添加gl={{ powerPreference: "high-performance" }}开启高性能模式。

3. 修正相机位置设置

在Model组件里用useEffect设置相机位置,确保只执行一次:

function Model(props){
  const {scene} = useGLTF("model.glb");
  const {camera} = useThree();

  useEffect(() => {
    camera.position.set(-180, 20, -160);
  }, [camera]);

  scene.traverse(child => {
    if (child.isMesh) {
        child.castShadow = true;
        child.receiveShadow = true;
    }
  });

  return <primitive object={scene} {...props} />;
}

4. 优化容器样式

给承载Canvas的容器添加will-change: transform,提示浏览器提前优化该元素的变换动画:

#fridge {
  will-change: transform;
}

修改后的完整代码示例

import React from 'react'
import Navbar from '../components/Navbar';
import {useRef, useEffect} from 'react';
import {useGLTF, Stage, OrbitControls} from '@react-three/drei';
import {Canvas, useThree} from '@react-three/fiber';
import { Suspense } from 'react'
import * as THREE from 'three';

function Model(props){
  const {scene} = useGLTF("model.glb");
  const {camera} = useThree();

  useEffect(() => {
    camera.position.set(-180, 20, -160);
  }, [camera]);

  scene.traverse(child => {
    if (child.isMesh) {
        child.castShadow = true;
        child.receiveShadow = true;
    }
  });

  return <primitive object={scene} {...props} />;
}

export default function Home(props) {
  const fridgeRef = useRef(null);
  const [scrollY, setScrollY] = React.useState(0);

  useEffect(() => {
    const handleScroll = () => {
      setScrollY(window.scrollY);
    };

    const updateFridgePosition = () => {
      if (fridgeRef.current) {
        fridgeRef.current.style.transform = `translateX(${-scrollY}px)`;
      }
      requestAnimationFrame(updateFridgePosition);
    };

    handleScroll();
    window.addEventListener("scroll", handleScroll);
    const animationId = requestAnimationFrame(updateFridgePosition);

    return () => {
      window.removeEventListener("scroll", handleScroll);
      cancelAnimationFrame(animationId);
    };
  }, [scrollY]);

  return (
    <section className="">
        <Navbar />
        <div className="flex items-center justify-center">
          <div className="flex flex-col md:flex-row w-9/12 justify-center items-center h-screen">
            <div className="flex items-center bg-gray-300 w-full">
              <div className="flex flex-col">
                  <h1 className="font-CreteRoundRegular text-5xl text-center md:text-left">KitchIN</h1>
                  <p className="font-CreteRoundRegular text-xl text-center md:text-left">A smart fridge that helps you keep track of your food.</p>
              </div>
            </div>
            <div className="md:w-1/2 w-full h-5/6 bg-red-200" ref={fridgeRef}>
              <Canvas frameloop="demand">
                <Suspense fallback={null}>
                  <Stage>
                    <Model />
                    <OrbitControls enableZoom={false} />
                  </Stage>
                </Suspense> 
              </Canvas>
            </div>
          </div>
        </div>
        <div className="flex h-screen">
            <h1>Hello</h1>
        </div>
    </section>
  )
}

useGLTF.preload("modern_fridge.glb");

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 00:06:21