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

使用react-router-dom v6+react-three-fiber时页面加载异常求助

React Router 与 React Three Fiber 路由问题

我用 react-three-fiber 开发网页,想通过最新版 react-router-dom 添加子页面,但主页面没法正常运行。想问下是不是必须把 Content、Startup 这些函数拆到独立文件里,不能在 App.js 里定义,路由才能正常工作?


App.js 代码

import ReactDOM from "react-dom"
import React, { Suspense, useEffect, useRef, useMemo } from "react"
import { Canvas, Dom, useLoader, useFrame } from "react-three-fiber"
import { TextureLoader, LinearFilter } from "three"
import lerp from "lerp"
import { Text, MultilineText } from "./components/Text"
import Plane from "./components/Plane"
import { Block, useBlock } from "./blocks"
import state from "./store"
import About from "./About"
import { Router, Link, Route, Routes } from "react-router-dom";
import "./styles.css"

export function Startup() {
  const ref = useRef()
  useFrame(() => (ref.current.material.opacity = lerp(ref.current.material.opacity, 0, 0.025)))
  return <Plane ref={ref} color="#0e0e0f" position={[0, 0, 200]} scale={[100, 100, 1]} />
}


export function Paragraph({ image, index, offset, factor, header, aspect, text, see, title }) {
  const { contentMaxWidth: w, canvasWidth, margin, mobile } = useBlock()
  const size = aspect < 1 && !mobile ? 0.65 : 0.9 //width of picture elements
  const alignRight = (canvasWidth - w * size - margin) / 2
  const pixelWidth = w * state.zoom * size
  const left = (index)
  const color = "#FFFFFF"
  return (
    <Block factor={factor} offset={offset}>
      <group center>
        <Plane map={image} args={[1, 1, 32, 32,]} shift={75} size={size} aspect={aspect} scale={[w * size, (w * size) / aspect, 1]} frustumCulled={false} />
        <Dom
          style={{ width: pixelWidth / (mobile ? 1 : 2), textAlign: left ? "left" : "right" }}
          position={[left || mobile ? (-w * size) / 2 : 0, (-w * size) / 2 / aspect - 0.4, 1]}>
          <div tabIndex={index}>{text}</div>
          <a href={see}>{title}</a>
        </Dom>
        <Text center size={w * 0.04} color={color}>
          {header}
        </Text>
      </group>
    </Block>
  )
}


export function Content() {
  const images = useLoader(
    TextureLoader,
    state.paragraphs.map(({ image }) => image)
  )
  useMemo(() => images.forEach(texture => (texture.minFilter = LinearFilter)), [images])
  const { contentMaxWidth: w, canvasWidth, canvasHeight, mobile } = useBlock()
  return (
    <>
    <group rotation-z={0.025} rotation-y={0.0} rotation-x={0.05}>
      <Block factor={1} offset={-0.1}>
      </Block>
      {state.paragraphs.map((props, index) => (
        <Paragraph key={index} index={index} {...props} image={images[index]} />
      ))}
      </group>
    </>
  )
}

export default function App() {
  const scrollArea = useRef()
  const onScroll = e => (state.top.current = e.target.scrollTop)
  useEffect(() => void onScroll({ target: scrollArea.current }), [])
  return (
    <>
      <Canvas className="canvas" concurrent pixelRatio={1} orthographic camera={{ zoom: state.zoom, position: [0, 0, 500] }}>
        <Suspense fallback={<Dom center className="loading" children="Loading..." />}>
          <Content />
          <Routes>
          <Route path="/about" element={<About />} />
        </Routes>
          <Startup />
        </Suspense>
      </Canvas>
      <div className="scrollArea" ref={scrollArea} onScroll={onScroll}>
        {new Array(state.sections).fill().map((_, index) => (
          <div key={index} id={"0" + index} style={{ height: `${(state.pages / state.sections) * 30}vh` }} />//site length
        ))}
        </div>
        <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
            <Link to="/about">About</Link>
          </li>
        </ul>
      </nav>
    </>
  );
}

index.js 代码

import "./styles.css"
import React from "react";
import { BrowserRouter, Routes, Route  } from "react-router-dom";
import App from "./App";
import { render } from "react-dom";

const rootElement = document.getElementById("root");
render(
 <BrowserRouter>
    <App />
 </BrowserRouter>,
  rootElement
  );

问题分析与解决方案

核心问题:路由位置错误

你把 <Routes> 放在了 <Canvas> 组件内部,这是主页面失效的关键原因。react-router-dom 的路由组件属于 ReactDOM 管理的 DOM 节点,而 <Canvas> 是 react-three-fiber 用来渲染 Three.js 3D 场景的容器,两者的渲染上下文完全不同,不能混用。

无需拆分组件到独立文件

完全不需要把 Content、Startup 这些组件移到单独文件,它们可以继续留在 App.js 中,只要调整路由结构即可。

修正后的代码示例

调整后的 App.js 核心部分

export default function App() {
  const scrollArea = useRef()
  const onScroll = e => (state.top.current = e.target.scrollTop)
  useEffect(() => void onScroll({ target: scrollArea.current }), [])
  return (
    <>
      {/* 路由放在 Canvas 外部,作为顶级组件管理页面视图切换 */}
      <Routes>
        {/* 首页路由:包含原有的 Canvas 和滚动区域 */}
        <Route path="/" element={
          <>
            <Canvas className="canvas" concurrent pixelRatio={1} orthographic camera={{ zoom: state.zoom, position: [0, 0, 500] }}>
              <Suspense fallback={<Dom center className="loading" children="Loading..." />}>
                <Content />
                <Startup />
              </Suspense>
            </Canvas>
            <div className="scrollArea" ref={scrollArea} onScroll={onScroll}>
              {new Array(state.sections).fill().map((_, index) => (
                <div key={index} id={"0" + index} style={{ height: `${(state.pages / state.sections) * 30}vh` }} />
              ))}
            </div>
          </>
        } />
        {/* About 页面路由:单独渲染 About 组件 */}
        <Route path="/about" element={<About />} />
      </Routes>
      {/* 导航栏放在路由外部,保持全局显示 */}
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
            <Link to="/about">About</Link>
          </li>
        </ul>
      </nav>
    </>
  );
}

关键修正点

  1. 将 <Routes> 移到 <Canvas> 外部,作为顶级组件管理整个页面的路由逻辑
  2. 首页路由 (/) 包含原有的 Canvas、滚动区域等内容,保持原有 3D 场景功能
  3. About 路由 (/about) 单独渲染 About 组件,实现页面切换
  4. 保留 Content、Startup 在 App.js 中,无需拆分

额外注意事项

  • 如果 About 页面需要使用 Three.js,需要在 About 组件内部单独嵌套 <Canvas>
  • 检查 state 对象的使用,避免路由切换时出现状态混乱

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 18:10:29