当前位置: 首页 > article >正文

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 防抖

以上就是解决思路 


http://www.kler.cn/a/584703.html

相关文章:

  • WebRTC技术在音视频处理上的难点剖析:EasyRTC嵌入式视频通话SDK的优化策略
  • Appium等待机制--强制等待、隐式等待、显式等待
  • 一次 诡异 的 JVM OOM 事故 原创
  • Vue3:组件通信方式
  • 【工具使用】IDEA社区版如何使用JDK原生命令:从IDEA到命令行的开发技巧
  • 完美解决ElementUI中树形结构table勾选问题
  • 商品管理中的“DeepSeek” AI赋能零售品牌释放利润空间
  • Spring Boot 常用注解的分类及简明解释
  • Spring Boot项目中集成sa-token实现认证授权和OAuth 2.0第三方登录
  • 50.HarmonyOS NEXT 登录模块开发教程(四):状态管理与数据绑定
  • 网络安全工具nc(NetCat)
  • Android7上移植I2C-tools
  • 探索 PyTorch 中的 ConvTranspose2d 及其转置卷积家族
  • SolidWorks中文完整版+教程百度云资源分享
  • 【JavaScript 】1. 什么是 Node.js?(JavaScript 服务器环境)
  • 【Flutter】第一次textEditingController.text获取到空字符串
  • 医院本地化DeepSeek R1对接混合数据库技术实战方案研讨
  • 性能优化:服务器性能影响网站加载速度分析
  • 如何从零编写自己的.NET IoT设备驱动
  • 第54天:Web攻防-SQL注入数据类型参数格式JSONXML编码加密符号闭合复盘报告