目录

React篇之three渲染

目录

React篇之three渲染

需求:拖拽右侧面板,里面的three模型能够自适应 import { useEffect, useState, useRef } from ‘react’ import ‘./App.css’ import * as THREE from ’three’; import { GLTFLoader } from ’three/addons/loaders/GLTFLoader.js’; import { debounce } from ’lodash’; const CanvasDemo = () => { let camera, scene, renderer, model, face; const [leftWidth, setLeftWidth] = useState(300); const leftWidthRef = useRef(leftWidth); const containerRef = useRef(null); const rightRef = useRef(null); const isDragging = useRef(false); // 更新 leftWidth 时同步更新 leftWidthRef useEffect(() => { leftWidthRef.current = leftWidth; }, [leftWidth]); const handleMouseDown = () => { isDragging.current = true; }; const handleMouseMove = (e) => { if (!isDragging.current) return; const containerRect = containerRef?.current?.getBoundingClientRect(); const newLeftWidth = e.clientX - containerRect.left; setLeftWidth(newLeftWidth); }; const handleMouseUp = () => { isDragging.current = false; }; const init = () => { camera = new THREE.PerspectiveCamera(45, (window.innerWidth - leftWidthRef.current) / window.innerHeight, 0.25, 100); camera.position.set(- 5, 3, 10); camera.lookAt(0, 2, 0); scene = new THREE.Scene(); scene.background = new THREE.Color(0xe0e0e0); scene.fog = new THREE.Fog(0xe0e0e0, 20, 100); const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3); hemiLight.position.set(0, 20, 0); scene.add(hemiLight); const dirLight = new THREE.DirectionalLight(0xffffff, 3); dirLight.position.set(0, 20, 10); scene.add(dirLight); const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2000, 2000), new THREE.MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false })); mesh.rotation.x = - Math.PI / 2; scene.add(mesh); const grid = new THREE.GridHelper(200, 40, 0x000000, 0x000000); grid.material.opacity = 0.2; grid.material.transparent = true; grid.position.set(0, 0, 0); // 将网格放置在场景中心 scene.add(grid); const loader = new GLTFLoader(); loader.load(‘ , function (gltf) { model = gltf.scene; scene.add(model); // 打印模型信息,调试用 console.log(‘Model loaded:’, model); }, undefined, function (e) { console.error(e); }); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth - leftWidthRef.current, window.innerHeight); rightRef.current?.appendChild(renderer.domElement); } const debouncedResize = debounce(() => { onWindowResize(); }, 10); // 100ms 防抖 const onWindowResize = () => { if (!camera || !renderer) return; camera.aspect = (window.innerWidth - leftWidthRef.current) / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth - leftWidthRef.current, window.innerHeight); // 确保动画持续运行 requestAnimationFrame(animate); } useEffect(() => { init(); animate(); // 启动渲染循环 if (rightRef.current) { const resizeObserver = new ResizeObserver(() => { if (rightRef.current) { debouncedResize(); } }); resizeObserver.observe(rightRef.current); return () => resizeObserver.disconnect(); } }, []) const animate = () => { requestAnimationFrame(animate); renderer.render(scene, camera); }; return (

左侧内容

); } export default CanvasDemo .container { display: flex; height: 100vh; user-select: none; } .left-pane { background-color: #f0f0f0; overflow: auto; } .divider { width: 5px; cursor: ew-resize; background-color: #ccc; } .right-pane { flex-grow: 1; background-color: #e0e0e0; overflow: auto; } 问题1:页面宽度变化第一时间都是window.onresize的事件,然而,resize 事件只在 window 对象(即由 document.defaultView 返回)上触发。只有在 window 对象上注册的处理器才能接收 resize 事件。 所以替换方法为:

const resizeObserver = new ResizeObserver(() => {});

resizeObserver.observe(dom);

return () => resizeObserver.disconnect(); 问题2:拖拽的时候,渲染模型会白屏闪烁,==>解决:加个防抖 const debouncedResize = debounce(() => {

onWindowResize();

}, 100); // 100ms 防抖 以上就是解决思路