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('https://threejs.org/examples/models/gltf/RobotExpressive/RobotExpressive.glb', 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 (
<div
ref={containerRef}
className="container"
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
>
<div className="left-pane" style={{ width: leftWidth }}>
左侧内容
</div>
<div className="divider" onMouseDown={handleMouseDown}></div>
<div ref={rightRef} className="right-pane"></div>
</div>
);
}
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 防抖
以上就是解决思路