docs: add Hero ink-data-morph animation design spec

This commit is contained in:
张翔
2026-05-03 16:38:08 +08:00
parent d51a99e645
commit fc328aab59
@@ -0,0 +1,195 @@
# Hero 数字水墨动画设计规格
> 日期:2026-05-03
> 状态:待审查
> 决策者:张翔
## 1. 需求与场景
### 背景
当前 Hero 区域(`hero-section-v2.tsx`)使用纯白背景 + 基础 FadeUp 文字动画,视觉表现力不足,缺乏品牌辨识度。
### 目标
为 Hero 区域添加"数字水墨"动画效果,传达"传统智慧 → 数字化转型"的品牌叙事。
### 成功标准
- Hero 入场时有明确的三阶段动画叙事(墨粒扩散 → 沉淀 → 数据化)
- 动画在 60fps 下流畅运行,移动端无卡顿
- `prefers-reduced-motion` 下优雅降级
- 动画播放完毕后零持续 CPU 开销
- 与现有 Hero 文字内容无视觉冲突
### 约束
- 纯入场动画,无鼠标/滚动交互
- 零新增外部依赖(纯 Canvas 2D API
- 遵循项目现有特效组件模式(`src/components/effects/`
## 2. 技术选型
**方案:Canvas 2D 粒子系统**
| 维度 | 评价 |
|------|------|
| 视觉效果 | 120-200 粒子 + 墨晕 + 连接线,效果完整 |
| 性能 | Canvas 2D 渲染 200 粒子无压力,移动端流畅 |
| 兼容性 | Canvas 2D 全浏览器支持,无 WebGL 依赖 |
| 包体积 | 零依赖,< 5KB gzipped |
| 可维护性 | 状态机驱动三阶段,逻辑清晰 |
| 项目一致性 | 与 DataParticleFlow、SubtleParticles 等现有组件模式一致 |
排除方案:SVG + CSS 动画(feTurbulence 滤镜移动端不稳定,三阶段叙事难以用 CSS 实现)
## 3. 组件设计
### 3.1 新组件:InkDataMorph
**文件**`src/components/effects/ink-data-morph.tsx`
```tsx
interface InkDataMorphProps {
particleCount?: number; // 默认 150
primaryColor?: string; // 墨色,默认 '#1C1C1C'
accentColor?: string; // 朱砂红,默认 '#C41E3A'
connectionColor?: string; // 连接线色,默认 '#C41E3A'
connectionDistance?: number; // 连接线触发距离,默认 80px
className?: string;
}
```
**渲染**:绝对定位 `<canvas>` 元素,`pointer-events: none``aria-hidden="true"`
### 3.2 三阶段状态机
```
Phase 1: SPREADING (0s ~ 2s)
├── 墨粒从两个中心点向外扩散
├── 粒子带有机随机速度 + 方向
├── 大粒子附带墨晕光晕(径向渐变)
└── 25% 粒子为朱砂红色
Phase 2: SETTLING (2s ~ 3.5s)
├── 粒子速度衰减至接近静止
├── 透明度缓慢降低(模拟墨汁沉淀)
└── 整体画面趋于稳定
Phase 3: MORPHING (3.5s ~ 6s)
├── 粒子缓慢移向目标数据点位置
├── 粒子半径缩小为数据点大小
├── 相邻粒子间浮现连接线(距离 < connectionDistance
└── 最终状态:静态数据网络图
COMPLETE (6s+)
├── 停止 requestAnimationFrame
└── 零持续 CPU 开销
```
### 3.3 粒子数据结构
```ts
interface Particle {
x: number; // 当前 x
y: number; // 当前 y
vx: number; // x 速度
vy: number; // y 速度
radius: number; // 当前半径
initialRadius: number; // 初始半径(墨粒大小)
dataRadius: number; // 数据点目标半径
opacity: number; // 当前透明度
isAccent: boolean; // 是否为朱砂红
phase: 'spreading' | 'settling' | 'morphing' | 'complete';
spreadTime: number;
maxSpreadTime: number;
settleTime: number;
morphProgress: number;
targetX: number; // 数据点目标 x
targetY: number; // 数据点目标 y
}
```
### 3.4 扩散中心点布局
```
┌─────────────────────────────────────────┐
│ ● 中心1 │
│ (70%, 35%) │
│ │
│ 文字区域 ←── │
│ │
│ ● 中心2 │
│ (25%, 65%) │
│ │
└─────────────────────────────────────────┘
```
- 中心1(右上):120 粒子,主墨色
- 中心2(左下):60 粒子,延迟 500ms 启动
- Phase 3 数据点目标位置分布在右侧和下方,不遮挡文字
### 3.5 性能策略
- **2x Canvas 渲染**`canvas.width = clientWidth * 2`CSS 缩放,Retina 清晰
- **连接线优化**:Phase 3 才计算,使用网格空间分区避免 O(n²)
- **一次性动画**Phase 3 完成后停止 rAF
- **`prefers-reduced-motion`**:跳过动画,直接渲染最终静态数据网络图
## 4. Hero Section 集成
### 4.1 修改文件
`src/components/sections/hero-section-v2.tsx`
### 4.2 变更点
| 项目 | 当前 | 改造后 |
|------|------|--------|
| 背景色 | `bg-white` | `bg-[#FAFAF5]`(宣纸暖白) |
| Canvas 层 | 无 | `<InkDataMorph />` 绝对定位 inset-0 z-0 |
| 内容层 z-index | 默认 | `z-10` |
| 品牌标签 delay | 0.1s | 1.6s |
| 标题 delay | 0.2s | 1.9s |
| 副标题 delay | 0.3s | 2.2s |
| 描述 delay | 0.4s | 2.5s |
| 按钮组 delay | 0.5s | 2.8s |
| IntersectionObserver | 保留 | 保留,只在进入视口时启动 Canvas 动画 |
| useReducedMotion | 保留 | 保留,禁用时 Canvas 直接渲染最终状态 |
### 4.3 导入方式
使用 `dynamic import` + `ssr: false`,与项目现有特效组件加载方式一致:
```tsx
const InkDataMorph = dynamic(
() => import('@/components/effects/ink-data-morph').then(mod => ({ default: mod.InkDataMorph })),
{ ssr: false }
);
```
## 5. 测试策略
### 5.1 单元测试(ink-data-morph.test.tsx
- 组件渲染 canvas 元素
- 接受自定义 propsparticleCount, colors 等)
- `prefers-reduced-motion` 下不启动 rAF
- unmount 时清理 rAF
### 5.2 集成测试(hero-section-v2.test.tsx 更新)
- InkDataMorph 在 Hero section 中渲染
- 文字内容 z-index 在 Canvas 之上
- 背景色为 `#FAFAF5`
### 5.3 视觉验证
- `npm run dev` 打开首页,观察三阶段动画
- 移动端视口(375px)验证粒子密度和性能
- `prefers-reduced-motion` 模拟验证降级效果
## 6. 文件清单
| 操作 | 文件 |
|------|------|
| 新建 | `src/components/effects/ink-data-morph.tsx` |
| 新建 | `src/components/effects/ink-data-morph.test.tsx` |
| 修改 | `src/components/sections/hero-section-v2.tsx` |
| 修改 | `src/components/effects/index.ts`(导出新组件) |