暂不支持移动端访问

BlogRoom 项目结构与 React Three Fiber 渲染架构

1513 字 8 分钟
AI 摘要

从 React 应用入口、Canvas、React DOM 到 Cloudflare Pages,说明 BlogRoom 的整体运行方式。

BlogRoom 是独立部署的 React + Three.js 个人主页。它把房间场景作为导航和交互层,但没有把所有界面都绘制到 WebGL:家具、相机和空间动画在 Canvas 中运行,文章、书籍详情、搜索、GitHub 信息、RSS 和播放器面板由 React DOM 渲染。两层通过 Zustand 状态和命令函数连接。

工程和依赖#

项目使用 Vite 8 作为开发服务器和生产构建工具,React 19、React DOM 19 负责界面,React Three Fiber 9 把 Three.js 场景映射为 React 组件。Three.js 0.185 提供渲染基础,@react-three/drei 提供 Text、MapControls 等常用对象,@react-three/postprocessing 和 postprocessing 实现线稿场景的后处理。

状态和数据层分别使用 Zustand 5、TanStack Query 5 和 Zod 4。GSAP 负责镜头和物件时间线,@react-three/rapier 负责字形物理。Node.js 版本范围是 >=24.9.0 <25,pnpm 版本范围是 >=10.33.0 <12,当前本地验证使用 Node.js 24.18.0 和 pnpm 11.17.0。

目录结构#

text
src/
├─ app/ React 根组件、错误边界、主题同步
├─ scene/ Canvas、相机、房间壳体、家具和后处理
├─ features/ 资料、GitHub、RSS、音乐、搜索和链接面板
├─ stores/ room-store、player-store 等 Zustand 状态
├─ api/ 浏览器请求封装和边缘 API 路由
├─ config/ profile、主题、房间数据和运行参数
├─ hooks/ 媒体查询、降级和 reduced-motion hooks
├─ types/ 房间、API 和 feature 的类型
└─ utils/ 命令、数据、存储、音频和 WebGL 工具

src/app/app.tsx 是运行时入口。它创建 QueryClient、错误边界和主题同步组件,再挂载 RoomExperienceRoomExperience 判断浏览器是否支持 WebGL,支持时渲染 RoomCanvas,不支持时渲染 RoomFallback;无论哪种情况,React DOM 的弹窗和播放器都可以继续工作。

Canvas 和 DOM 的边界#

RoomCanvas 创建 R3F <Canvas>,开启正交相机、按主题设置 DPR 和背景色,并使用 frameloop="demand" 减少没有状态变化时的渲染。Suspense 包住 RoomScene,场景资源尚未准备好时不输出半成品对象;SceneReady、字形 warmup 和 line reveal 共同决定房间何时显示。

TSX
// 限制 DPR 并按需渲染,避免静态线稿场景持续占用 GPU。
<Canvas
orthographic
dpr={[1, theme === 'dark' ? 1.5 : 1.75]}
frameloop="demand"
camera={{ far: 140, near: 0.1, position: [20, 14.5, 22], zoom: 58 }}
>
<Suspense fallback={null}>
{/* 场景挂载和字形预热都完成后,才允许移除房间加载层。 */}
<RoomScene ... />
<SceneReady onReady={onSceneReady} />
</Suspense>
</Canvas>

Canvas 使用 aria-hidden,因为它的点击入口同时有命令和 DOM 面板。RoomFallback 提供静态 SVG 房间预览,WebGL 不可用时不能只留下空白区域。新增交互时,应该为 Canvas 物件提供对应的 DOM 操作入口或 fallback 行为,不能只依赖 hover 和鼠标点击。

App 的状态编排#

createQueryClient() 将窗口聚焦重新请求关闭,失败只重试一次,数据五分钟内视为新鲜。面板打开后,feature 组件通过 TanStack Query 请求 API;请求失败只影响面板,不卸载 Canvas。AppErrorBoundary 负责捕获 React 渲染异常,防止一个外部数据卡片把整个房间变成白屏。

RoomExperience 根据 panelisDoorExitPromptOpen 推导 activeCameraFocus。桌面端通过 focusObject() 让相机移动到面板对应物件,移动端不强制聚焦,而是保留 MapControls。场景、面板和相机没有互相直接调用,而是通过 store 中的状态完成协调。

构建和边缘部署#

pnpm build 先运行 sync:room-data,再执行 TypeScript 项目构建和 Vite bundle。生产环境部署到 Cloudflare Pages,functions/api/[path].ts/api/* 请求转给与本地相同的 edge router。GitHub token 等凭据只保存在 Pages 环境变量,浏览器只能得到经过校验的公开 JSON。

静态资源、字体和房间数据在构建时进入产物,RSS、GitHub、音乐和健康检查在运行时由边缘函数请求。修改部署配置时要区分这两类资源:构建时错误会让 bundle 失败,边缘接口错误则应返回统一的 ApiError,不影响静态房间加载。

维护入口#

改 React 外壳看 src/app/;改房间渲染看 src/scene/;改弹窗和业务功能看 src/features/;改房间交互状态看 src/stores/room-store.tssrc/utils/room-commands.ts;改 API 看 src/api/http.tssrc/api/edge/ 和 Pages Function;改共享书籍/友链数据看同步脚本和 src/config/room-data/

验证命令#

Terminal windowpowershell
# 运行当前 Vitest 工具用例。
pnpm test
# 检查 React、R3F、API 和 TypeScript 类型。
pnpm typecheck
# 运行 ESLint 与 Stylelint,禁止遗留 warning。
pnpm lint
# 同步房间数据、编译 TypeScript 并生成 Vite 生产 bundle。
pnpm build

测试当前只有 1 个 Vitest 文件、2 个用例,主要覆盖工具函数。涉及 Canvas、浏览器 pointer、音频或 Pages Function 的修改,需要额外在桌面端、移动端和无 WebGL 环境检查。

渲染循环和资源加载#

Canvas 使用 frameloop="demand",静态场景不会像默认模式一样每帧渲染。相机动画、线稿揭示、字形物理和正在播放的物件需要通过 R3F 的 invalidate 或 useFrame 请求新帧;动画结束后应停止请求。新增一个会持续变化的组件时,如果直接在 render 中创建定时器,容易导致重复计时和高 CPU 占用,应把时序放进 effect 并在 cleanup 中取消。

Suspense 只负责等待 React Three Fiber 可挂载的异步资源,不能替代业务层的 ready 状态。房间揭示前需要确认 Canvas 已创建、字体 warmup 完成、场景组件已挂载,RoomLoader 才移除加载层。慢速设备上如果把这些条件合并为一次 onCreated,容易出现字体突然跳入或线稿动画提前结束。

配置和环境变量#

src/config/ 中的 profile、主题、房间 JSON 和默认 camera zone 会进入客户端 bundle;它们不能保存密钥。GitHub token、RSS 上游地址中需要保护的参数和音乐访问凭据只在 Cloudflare Pages Function 环境变量中使用。.env.example 只列出变量名称和说明,不应填入真实值。

生产构建通过 functions/api/[path].ts 进入 edge router,本地 Vite 通过 vite.config.ts 复用同一分发函数。修改路由路径时要同时检查两种入口和 Cloudflare Pages 的 Function 文件名。

[ 公告 ]

如果你喜欢,那么欢迎来到我的世界!

了解更多
[ 音乐 ]
封面

音乐

找不到相关结果。
[ 目录 ]
[ 全部文章 ]