滚动时3D模型频繁刷新问题求助(React+Three.js)
解决滚动时React Three Fiber模型频繁刷新问题
问题分析
你的代码存在几个导致3D模型频繁刷新的核心问题:
- 重复且低效的滚动监听:两个
useEffect都绑定了scroll事件,第二个监听里的循环会在每次滚动时多次修改DOM样式,触发不必要的重绘/重排,进而导致Canvas频繁刷新。 - 相机位置设置逻辑不当:在
Model组件里直接用useThree设置相机位置,这个函数会在每帧渲染时执行,干扰正常渲染流程。 - 父元素样式更新触发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
相关产品推荐
相关产品推荐

