Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Enter Cyberspace

基于 Three.js 的交互式 hover 效果原型。鼠标悬停按钮时触发涟漪、倾斜与视差;点击 EXPLORE 进入沉浸式赛博空间隧道动画。

Enter Cyberspace 沉浸模式 — 赛博空间隧道效果

点击 EXPLORE 进入的沉浸式隧道场景

技术栈

  • React 19 — UI 层
  • Vite 6 — 构建与开发服务器
  • Three.js — WebGL 渲染(按钮 shader、隧道、粒子、漂浮物)
  • GSAP — 过渡与 UI 动画
  • SCSS — 样式(CSS Modules + 共享变量 / mixin)

功能

  • 按钮 hover:涟漪扩散、3D 倾斜、背景视差
  • 点击沉浸:按钮扩展至全屏,进入隧道场景
  • 键盘支持:Escape 退出沉浸模式
  • 移动端适配:低功耗模式、触摸交互
  • 按需渲染:空闲时停止 animation loop,节省 GPU

快速开始

环境要求

  • Node.js 18+
  • npm / pnpm / yarn

安装与运行

npm install
npm run dev

浏览器访问终端输出的本地地址(默认 http://localhost:5173)。

构建与预览

npm run build
npm run preview

产物输出至 dist/

项目结构

EnterCyberspace/
├── public/                 # 静态资源(favicon 等)
├── src/
│   ├── main.jsx            # React 入口
│   ├── App.jsx             # 根组件
│   ├── components/
│   │   ├── Header/         # 页头
│   │   └── ExploreScene/   # WebGL 画布 + EXPLORE 按钮
│   ├── hooks/
│   │   └── useHoverEffect.js   # React ↔ Three.js 桥接
│   ├── engine/             # 3D 引擎(与 React 解耦)
│   │   ├── HoverEffectEngine.js
│   │   ├── button/         # 按钮 mesh 与 shader
│   │   ├── core/           # 相机、渲染器
│   │   ├── space/          # 隧道、粒子、漂浮物
│   │   ├── config/         # 可调参数
│   │   ├── shaders/        # GLSL 源码
│   │   └── utils/
│   └── styles/
│       ├── _variables.scss
│       ├── _mixins.scss
│       └── global.scss
├── index.html
└── vite.config.js

架构说明

React 负责 DOM 结构与样式;Three.js 逻辑封装在 HoverEffectEngine 中,通过 useHoverEffect hook 在组件挂载时初始化、卸载时释放 WebGL 资源。

ExploreScene (React)
    └── useHoverEffect
            └── HoverEffectEngine
                    ├── ButtonManager   → 按钮 shader / 涟漪
                    ├── SpaceManager    → 隧道场景 render target
                    ├── SceneCamera
                    └── SceneRenderer

实现过程说明

1. 整体演进

项目最初为纯 JavaScript + Vite 实现:HTML 中手写 DOM 结构,App 类通过 data-* 选择器绑定元素并驱动 Three.js。后续重构为 React 负责 UI、引擎层独立 的分层结构:

阶段 内容
原型 Three.js 全屏 canvas + DOM 按钮叠层,class 驱动交互
引擎拆分 将按钮、空间、相机、渲染器拆为独立模块,配置集中至 config/
React 化 用组件替代静态 HTML,通过 ref 注入 DOM,useHoverEffect 管理生命周期
样式 CSS 迁移为 SCSS,提取 _variables.scss / _mixins.scss 复用断点与颜色

React 不直接操作 Three.js 对象,只提供挂载点;所有 WebGL 逻辑仍由 HoverEffectEngine 统一管理,避免 React 重渲染与 rAF 循环冲突。

2. 渲染管线:双层场景

核心视觉由 两次 render 组成:

┌─────────────────────────────────────────┐
│  主场景 (Scene)                          │
│  └── ButtonManager.mesh (ShaderMaterial) │
│       └── 采样 uTexture ←───────────────┼── SpaceManager RenderTarget
└─────────────────────────────────────────┘
  1. SpaceManager(离屏) — 隧道、灯光、漂浮物、粒子渲染到 WebGLRenderTarget,输出纹理 texture
  2. ButtonManager(主场景) — 带圆角的 Plane + 自定义 fragment shader,将 texture 作为 uTexture 采样,叠加玻璃质感、涟漪、冲击波等效果后输出到屏幕

仅当 uHover > 0.01uProgress > 0.01(即 hover 或沉浸中)时才执行离屏渲染,空闲时不绘制隧道场景。

3. 按钮交互实现

3.1 UV 拾取

鼠标/触摸位置经 Raycaster 投射到按钮 mesh,得到 UV 坐标:

// HoverEffectEngine.pickUv
this.pointer.set(
  (clientX / viewport.width) * 2 - 1,
  -(clientY / viewport.height) * 2 + 1,
);
this.three.raycaster.setFromCamera(this.pointer, this.three.camera);
return raycaster.intersectObject(buttonManager.mesh)[0]?.uv;

UV 用于:涟漪原点(uOrigin)、3D 倾斜方向、空间视差偏移。

3.2 涟漪系统

ButtonManager 维护最多 6 个涟漪(Vector4 数组),通过 uniform uRipples 传入 fragment shader。shader 内用 SDF 圆环 + 噪声计算高度场,再做法线扰动实现折射感。

  • 进入 hover:在指针位置生成一次强涟漪(RIPPLE.enterStrength
  • 移动中:指针移动距离超过 minDistance 时按速度生成弱涟漪
  • 点击沉浸:触发 triggerShock() 冲击波动画

3.3 Hover 状态

setHover(true) 时:

  • GSAP 驱动 uHoveruRefract uniform 过渡
  • 根据 UV 偏移设置 mesh 的 rotation(倾斜)
  • SpaceManager.setParallax() 更新相机微旋转,产生视差
  • DOM 按钮设置 data-hover="true" 切换文字颜色;GSAP 控制阴影透明度

指针离开按钮区域(含 8px 容差)或 pointerleave 时调用 releaseHover() 复位。

4. 沉浸模式实现

点击按钮或 canvas 上命中 UV 时调用 setImmerse(true)

  1. 按钮 mesh 动画 — GSAP 将 scale / position 从 DOM 按钮的屏幕坐标映射值过渡到全屏尺寸(playImmerse
  2. uniform uProgress — 0→1 驱动 shader 内 portal 开门、背景显现
  3. SpaceManagersetDive(progress) 沿 Z 轴推进相机并放大 FOV;setReduced(true) 降低粒子等层的开销
  4. RenderTarget 扩容 — 沉浸时按屏幕分辨率 resize(上限 RENDER.rtCap = 2048),退出后恢复默认尺寸
  5. DOM 按钮 — GSAP 淡出(autoAlpha: 0),避免与 WebGL 层重叠

退出:Escape、再次点击、或 touch pointerup 触发 setImmerse(false),反向播放上述动画。

5. 空间场景组成

SpaceManager 按层组织子模块,均在 update(time) 中逐帧更新:

模块 职责
SpaceLights 场景灯光
Tunnel 隧道几何 + shader,营造纵深
Floaters 漂浮装饰物
Particles 粒子流(移动端可 setLowPower 降采样)

相机视差:setParallax(x, y) 写入目标偏移,每帧 lerp 平滑后应用到 camera.rotation

6. 坐标对齐:DOM 按钮 ↔ WebGL mesh

layoutButton() 是关键桥接步骤:

  1. 读取 DOM 按钮的 getBoundingClientRect()
  2. 结合 SceneCamera.getViewSize() 将像素坐标转换为 Three.js 世界空间中的 position / scale
  3. 读取 CSS border-radius 换算为 shader 的 uCorner(圆角 SDF)

窗口 resize 时重新 layout,保证 WebGL 按钮与 DOM 按钮视觉对齐;沉浸期间跳过 layout,由全屏动画接管。

7. 性能优化

  • 按需 rAFrenderer.setAnimationLoop 仅在交互或动画进行中运行;isIdle() 检测 hover、沉浸、涟漪、GSAP tween 全部结束后停止循环
  • 像素比上限:移动端最高 1.5,桌面 2.0(getPixelRatio()
  • visibility API:标签页隐藏时停止渲染
  • WebGL context 丢失:监听 webglcontextlost / restored 暂停与恢复
  • debounce resize:150ms 防抖后再更新 pixelRatio,避免拖拽窗口时频繁重建 buffer

8. React 集成要点

// ExploreScene.jsx — 仅提供 ref,不参与 3D 状态
const canvasRef = useRef(null);
const buttonRef = useRef(null);
useHoverEffect(canvasRef, buttonRef);
// useHoverEffect.js — 挂载/卸载对称
useEffect(() => {
  const engine = new HoverEffectEngine({ canvasEl, buttonEl });
  engine.load();
  return () => engine.dispose();  // 移除 listener、dispose renderer
}, [canvasRef, buttonRef]);

注意:data-hover、opacity 等由引擎通过 GSAP 直接写 DOM,不放入 React state,避免多余重渲染。

9. 扩展建议

  • 调参:优先修改 src/engine/config/button.jsspace.js
  • 新增空间层:实现 update(time) / objects 接口,注册到 SpaceManager.layers
  • _shader 改动:编辑 src/engine/shaders/*.glsl,Vite ?raw 导入无需额外 loader

配置

主要参数位于 src/engine/config/

文件 内容
button.js 涟漪、hover 倾斜、渲染上限
space.js 隧道、粒子、漂浮物参数
media.js 移动端断点 media query

脚本

命令 说明
npm run dev 启动开发服务器
npm run build 生产构建
npm run preview 本地预览构建结果

License

Private — 仅供学习与原型演示使用。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages