6.4 KiB
6.4 KiB
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
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 粒子数据结构
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,与项目现有特效组件加载方式一致:
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 元素
- 接受自定义 props(particleCount, 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(导出新组件) |