在 Nextjs 14 中使用 Three.js - ReferenceError:文档未定义

问题描述 投票:0回答:1

我知道这个特定错误已被多次询问,但我认为这种情况足够独特,值得单独发表。

所以我刚刚开始思考 nextjs 14 中的 Three.js。 一切似乎都在本地完美运行,但我仍然看到以下错误: ReferenceError:文档未定义

我认为这里有一个点,我想早期使用文档来创建画布,但我还没有找到它,因为我想它将在 Threejs 代码本身中。

这里有人做过类似的事情并且知道这个问题在哪里触发吗?

"use client";
export const dynamic = "auto";
import { ISSPosition, fetchISSPosition } from "@/lib/api";
import { convertCoordsToSphere } from "@/lib/functions";
import React, { useRef, useEffect, useState } from "react";
import * as THREE from "three";

const earthRadius = 10;
const scene = new THREE.Scene();

// Fog setup
...

// Camera and renderer setup
...

const renderer = new THREE.WebGLRenderer({
  alpha: true,
  antialias: true,
});

// Lighting setup
...

// Earth model setup
const geometry = new THREE.SphereGeometry(earthRadius, 64, 64);
const earthMat = new THREE.MeshPhongMaterial({
  shininess: 350,
  map: new THREE.TextureLoader().load("/earth_texture.jpg"),
  bumpMap: new THREE.TextureLoader().load("/earth_texture.jpg"),
});
const earth = new THREE.Mesh(geometry, earthMat);
earth.receiveShadow = true;
earth.rotation.x = 0.3;
scene.add(earth); // Add Earth to the scene upfront

// ISS model setup
...

const animate = () => {
  requestAnimationFrame(animate);
  earth.rotation.y -= 0.001;

  renderer?.render(scene, camera);
};

const ThreeScene = () => {
  const [issPosition, setIssPosition] = useState<ISSPosition>();
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const fetchISSData = async () => {
      setIssPosition(await fetchISSPosition());
    };

    const intervalId = setInterval(fetchISSData, 5000);

    if (typeof window !== "undefined") {
      containerRef.current?.appendChild(renderer.domElement);
    }

    animate();

    const handleResize = () => {
      if (renderer) {
        let size =
          window.innerWidth * 0.8 > 800 ? 800 : window.innerWidth * 0.8;
        renderer.setSize(size, size);
      }
    };

    handleResize();
    window.addEventListener("resize", handleResize);

    return () => {
      clearInterval(intervalId);
    };
  }, []);

  useEffect(() => {
    const animate = () => {
      requestAnimationFrame(animate);

      if (issPosition) {
        const pointCoords = convertCoordsToSphere(
          Number(issPosition.iss_position.latitude),
          -Number(issPosition.iss_position.longitude),
          earthRadius
        );

        issPoint.position.copy(pointCoords.applyMatrix4(earth.matrix));
        scene.add(issPoint);
      }

      renderer.render(scene, camera);
    };

    animate();
  }, [issPosition]);

  return (
    typeof window !== "undefined" && (
      <>
        <div className="m-auto max-w-fit" ref={containerRef}></div>
        {issPosition ? (
          <div className="flex flex-col gap-2 mb-8 w-fit justify-center items-center mx-auto">
            <span>Latitude: {issPosition?.iss_position.latitude}</span>
            <span>Longitude: {issPosition?.iss_position.longitude}</span>
          </div>
        ) : (
          <div className="flex gap-4 justify-center">
            <p className="mb-4">Locating ISS</p>
            <i className="border-b- border-primary w-4 h-4 rounded-full animate-spin"></i>
          </div>
        )}
      </>
    )
  );
};

export default ThreeScene;

javascript next.js three.js
1个回答
0
投票

这个问题的原因是,

Next js 默认执行服务端渲染。这意味着您的 JavaScript 代码先在服务器上执行,然后再发送到客户端的浏览器。由于

document
对象是浏览器 API 的一部分,并且在服务器上不存在,因此当您的代码尝试在 SSR 期间访问它时,您会遇到此错误。

即使认为在本地环境中似乎没问题,因为您执行

npm run dev
,该应用程序将无法在生产环境中运行。尝试运行
npm build
,然后它会显示此错误并希望完成构建。因此,要构建,您需要解决问题。

您遇到的

document
错误可能源于 Three.js 场景的初始化,尤其是渲染器,因为
THREE.WebGLRenderer()
使用
document
创建
<canvas>
元素。由于这发生在任何客户端检查或生命周期方法之外,因此它在服务器端上下文中进行评估,其中
document
未定义。

要解决此问题,您应该确保使用

document
或其他特定于浏览器的 API 的任何代码仅在客户端执行。在 Next.js 中,这通常是通过使用
if (typeof window !== "undefined") { ... }
之类的检查来保护此类代码来处理的,您已经对部分代码进行了检查。

但是,渲染器是在任何组件生命周期方法或客户端检查之外创建的,从而导致 SSR 问题。

我修改了你的代码,

const ThreeScene = () => {
  const [issPosition, setIssPosition] = useState<ISSPosition>();
  const containerRef = useRef<HTMLDivElement>(null);
  const [renderer, setRenderer] = useState<THREE.WebGLRenderer | null>(null);

  useEffect(() => {
    // Ensure this runs only on the client side
    if (typeof window !== "undefined" && !renderer) {
      const newRenderer = new THREE.WebGLRenderer({
        alpha: true,
        antialias: true,
      });
      setRenderer(newRenderer);
      containerRef.current?.appendChild(newRenderer.domElement);
    }
  }, [renderer]);

  useEffect(() => {
    const fetchISSData = async () => {
      setIssPosition(await fetchISSPosition());
    };

    const intervalId = setInterval(fetchISSData, 5000);

    // Move the animation code inside this useEffect
    if (renderer && typeof window !== "undefined") {
      const animate = () => {
        requestAnimationFrame(animate);
        earth.rotation.y -= 0.001;
        renderer.render(scene, camera);
      };

      animate();

      const handleResize = () => {
        let size = window.innerWidth * 0.8 > 800 ? 800 : window.innerWidth * 0.8;
        renderer.setSize(size, size);
      };

      handleResize();
      window.addEventListener("resize", handleResize);
    }

    return () => {
      clearInterval(intervalId);
    };
  }, [renderer, issPosition]); // Added renderer to dependency array

  // The rest of your component...
};
© www.soinside.com 2019 - 2024. All rights reserved.