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

以上就是解决思路 

React Native 中,使用 Three.js 渲染器需要使用 `react-native-webgl` 库,而该库中的 WebGLRenderer 并不支持设置渲染器的位置。不过我们可以通过将 canvas 元素包裹在一个 View 中,再设置该 View 的样式来实现类似的效果。 具体的实现方式如下: ```jsx import React, { useRef, useEffect } from 'react'; import { View } from 'react-native'; import { GLView } from 'react-native-webgl'; import * as THREE from 'three'; const Example = () => { const rendererRef = useRef(null); useEffect(() => { if (rendererRef.current) { const renderer = rendererRef.current.getContext('webgl', { antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 5; const scene = new THREE.Scene(); const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); const cube = new THREE.Mesh(geometry, material); scene.add(cube); const animate = () => { requestAnimationFrame(animate); cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render(scene, camera); }; animate(); } }, []); return ( <View style={{ position: 'absolute', top: 50, left: 50 }}> <GLView style={{ width: 300, height: 300 }} ref={rendererRef} /> </View> ); }; export default Example; ``` 在上面的代码中,我们将 `GLView` 组件(即 Three.js 渲染器的 canvas 元素)放在了一个 `View` 组件中,并设置了该 `View` 组件的样式来控制渲染器的输出位置。需要注意的是,由于 `GLView` 组件是异步加载的,因此我们需要在 useEffect 中等待它被加载完毕之后才能对其进行操作。 最后,我们通过 `renderer.render(scene, camera)` 方法将场景渲染到屏幕上,并通过 `requestAnimationFrame` 实现了动画效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值