You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将纯Three.js项目实现为Next.js中的React组件并解决渲染异常

问题修复方案

以下是你代码的核心问题和修复后的完整可运行代码:

核心问题列表

  • 缺失Next.js客户端组件标识:Next.js默认启用服务端渲染,涉及浏览器原生API(canvas、window等)的组件必须显式声明为客户端组件
  • 模块级全局变量污染:scene、renderer等实例定义在组件外部,多次挂载组件会出现变量覆盖、资源泄漏
  • 纹理异步加载未触发重绘:Three.js纹理加载是异步操作,加载完成前已经渲染第一帧,后续无自动更新导致黑屏
  • 缺少组件卸载清理逻辑:未销毁渲染实例、取消动画帧请求,会引发内存泄漏和渲染异常
  • 未适配高DPI屏幕:未设置渲染器像素比,高分辨率设备下会出现显示异常

修复后完整代码

'use client' // Next.js 13+ App Router必须加,标记为客户端组件,Pages Router可省略
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import React, { useEffect, useRef } from 'react';

import px from './cubemap/px.jpg';
import nx from './cubemap/nx.jpg';
import py from './cubemap/py.jpg';
import ny from './cubemap/ny.jpg';
import pz from './cubemap/pz.jpg';
import nz from './cubemap/nz.jpg';

const images = [px, nx, py, ny, pz, nz];

// 材质创建函数,加入加载完成回调
function createMaterialArray(onLoadComplete) {
  const loader = new THREE.TextureLoader();
  const loadedCount = { value: 0 };
  const materialArray = images.map((image) => {
    const texture = loader.load(image, () => {
      loadedCount.value++;
      // 所有纹理加载完成后触发回调重绘
      if (loadedCount.value === 6 && onLoadComplete) onLoadComplete();
    });
    return new THREE.MeshBasicMaterial({
      map: texture,
      side: THREE.BackSide,
    });
  });
  return materialArray;
}

export default function Universe() {
  const containerRef = useRef(null);
  // 用useRef存储实例,避免变量泄漏
  const sceneRef = useRef(null);
  const cameraRef = useRef(null);
  const rendererRef = useRef(null);
  const controlsRef = useRef(null);
  const animationIdRef = useRef(null);

  useEffect(() => {
    if (!containerRef.current) return;

    // 所有实例移到useEffect内部,避免全局污染
    const scene = new THREE.Scene();
    sceneRef.current = scene;

    const camera = new THREE.PerspectiveCamera(
      55,
      window.innerWidth / window.innerHeight,
      45,
      30000,
    );
    cameraRef.current = camera;

    const renderer = new THREE.WebGLRenderer({ antialias: true });
    // 适配高DPI屏幕
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    containerRef.current.appendChild(renderer.domElement);
    rendererRef.current = renderer;

    // 创建材质,纹理全部加载完成后自动触发重绘
    const materialArray = createMaterialArray(() => {
      renderer.render(scene, camera);
    });

    const skyboxGeo = new THREE.BoxGeometry(10000, 10000, 10000);
    const skybox = new THREE.Mesh(skyboxGeo, materialArray);
    scene.add(skybox);

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enabled = true;
    controls.minDistance = 100;
    controls.maxDistance = 1500;
    controls.autoRotate = true;
    controls.autoRotateSpeed = 1.0;
    controlsRef.current = controls;

    camera.position.set(1200, -250, 200);

    // 动画循环
    function animate() {
      controls.update();
      renderer.render(scene, camera);
      animationIdRef.current = requestAnimationFrame(animate);
    }
    animate();

    // 窗口 resize 适配
    function handleResize() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    }
    window.addEventListener('resize', handleResize);

    // 卸载清理逻辑,避免内存泄漏
    return () => {
      window.removeEventListener('resize', handleResize);
      if (animationIdRef.current) cancelAnimationFrame(animationIdRef.current);
      if (renderer.domElement) containerRef.current?.removeChild(renderer.domElement);
      renderer.dispose();
      controls.dispose();
      materialArray.forEach(mat => {
        mat.map.dispose();
        mat.dispose();
      });
      skyboxGeo.dispose();
    };
  }, []);

  return <div ref={containerRef} style={{ width: '100vw', height: '100vh', margin: 0, padding: 0, overflow: 'hidden' }}></div>;
}

额外注意事项

  • 如果你把立方体贴图放在Next.js的public目录下,引用路径不需要加./,直接写/cubemap/px.jpg即可
  • 可以先替换纯色材质测试基础渲染逻辑是否正常,排除图片路径错误的问题

内容的提问来源于stack exchange,提问作者ambrecht-web

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 14:06:05