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

React(Next.js)使用Three.js创建的Mesh重复渲染且页面有滚动条如何解决

问题根源

  1. 你的代码直接把Three.js初始化逻辑写在组件函数体中,React组件每次重渲染都会执行这段逻辑,重复创建场景、渲染器实例,每次都往body插入新的canvas元素,所以会出现多个立方体。
  2. 滚动条是因为浏览器默认给body设置了margin,加上canvas默认是inline元素存在基线偏移,导致canvas实际占位尺寸超出视口。

解决方案

第一步:添加全局样式(放入项目的全局css文件,比如globals.css)

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  overflow: hidden;
}
canvas {
  display: block;
}

第二步:修改组件代码

'use client' // 仅Next.js 13+ App Router版本需要加这一行
import * as THREE from 'three';
import { useEffect, useRef } from 'react';

function Index() {
  // 存储容器节点的引用
  const containerRef = useRef(null);

  useEffect(() => {
    // 仅组件挂载时执行一次初始化逻辑
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      1000
    );

    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(window.devicePixelRatio);
    // 插入到组件内部容器,不直接操作body
    containerRef.current.appendChild(renderer.domElement);

    const geometry = new THREE.BoxGeometry();
    const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    camera.position.z = 12;
    renderer.render(scene, camera);

    // 组件卸载时清理资源,避免内存泄漏
    return () => {
      renderer.dispose();
      containerRef.current.removeChild(renderer.domElement);
      geometry.dispose();
      material.dispose();
    };
  }, []); // 空依赖数组保证初始化逻辑仅执行一次

  return <div ref={containerRef}></div>;
}

export default Index;

说明

  • 空依赖的useEffect只会在客户端组件挂载完成后执行一次,不会随组件重渲染重复执行,从根源避免了重复创建实例的问题。
  • 把canvas插入到组件内部的ref容器中,符合React的DOM管理逻辑,不会在body中残留多余节点。
  • 全局样式重置了默认边距、设置body溢出隐藏,canvas设为块级元素,完全避免滚动条出现。

内容的提问来源于stack exchange,提问作者Axisnix

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 10:57:03