概述
SwipeMultiContainer
是一个高性能的多模板垂直滑动容器组件,实现了类似抖音的滑动切换效果。本文档详细介绍其核心算法原理、数学公式和实现机制。
核心特性
- 🎯 多模板同时渲染:同时显示前/当前/后三个模板
- 📱 跟手滑动:实时跟随手指移动的物理感知
- 🎮 智能切换:基于距离和速度的智能判断算法
- ⚡ 性能优化:仅渲染可见的3个模板
- 🎨 平滑动画:贝塞尔曲线动画和过渡效果
- 🛡️ 交互保护:自动忽略按钮、链接等交互元素
- 🚪 边缘滑动关闭:支持从左边缘右滑关闭
- 🎯 智能边缘检测:自动识别边缘滑动手势
算法架构
graph TD
A["触摸开始"] --> B{"边缘检测"}
B -->|"左边缘"| C["启用关闭模式"]
B -->|"非边缘"| D["启用切换模式"]
C --> E["水平滑动处理"]
D --> F["垂直滑动处理"]
E --> G["阻尼计算"]
F --> H["阻尼计算"]
G --> I["实时变换"]
H --> J["实时变换"]
I --> K["触摸结束"]
J --> K
K --> L{"智能吸附判断"}
L -->|"关闭"| M["滑出动画"]
L -->|"切换"| N["切换动画"]
L -->|"回弹"| O["回弹动画"]
M --> P["执行关闭"]
N --> Q["更新索引"]
O --> R["恢复原位"]
style A fill:#4a90e2,stroke:#2171b5,color:#fff
style B fill:#f39c12,stroke:#d68910,color:#fff
style L fill:#e74c3c,stroke:#c0392b,color:#fff
style P fill:#27ae60,stroke:#229954,color:#fff
style Q fill:#27ae60,stroke:#229954,color:#fff
style R fill:#27ae60,stroke:#229954,color:#fff
核心算法详解
1. 触摸事件处理算法
1.1 触摸状态管理
typescript
interface TouchState {
startX: number; // 起始X坐标
startY: number; // 起始Y坐标
currentX: number; // 当前X坐标
currentY: number; // 当前Y坐标
startTime: number; // 开始时间戳
lastMoveTime: number; // 最后移动时间
velocityY: number; // Y轴速度
velocityX: number; // X轴速度
isDragging: boolean; // 是否正在拖拽
initialOffset: number; // 初始偏移量
direction?: SwipeDirection; // 滑动方向
isFromLeftEdge?: boolean; // 是否从左边缘开始
isFromRightEdge?: boolean; // 是否从右边缘开始
}
1.2 速度计算公式
速度计算采用时间差分法:
ini
velocityX = (currentX - previousX) / timeDelta
velocityY = (currentY - previousY) / timeDelta
其中:
timeDelta = currentTime - lastMoveTime
- 单位:像素/毫秒 (px/ms)
2. 边缘检测算法
2.1 边缘检测公式
typescript
const containerRect = containerRef.current.getBoundingClientRect();
const relativeX = touch.clientX - containerRect.left;
const isFromLeftEdge = relativeX <= edgeDetectionWidth;
const isFromRightEdge = relativeX >= (containerRect.width - edgeDetectionWidth);
2.2 边缘检测流程
flowchart TD
A["获取触摸点坐标"] --> B["计算相对位置"]
B --> C{"relativeX ≤ edgeWidth?"}
C -->|"是"| D["标记为左边缘"]
C -->|"否"| E{"relativeX ≥ (width - edgeWidth)?"}
E -->|"是"| F["标记为右边缘"]
E -->|"否"| G["标记为中心区域"]
D --> H["启用关闭手势"]
F --> I["禁用关闭手势"]
G --> I
style A fill:#3498db,stroke:#2980b9,color:#fff
style D fill:#e74c3c,stroke:#c0392b,color:#fff
style F fill:#f39c12,stroke:#d68910,color:#fff
style G fill:#95a5a6,stroke:#7f8c8d,color:#fff
3. 滑动方向检测算法
3.1 方向判断公式
typescript
const getSwipeDirection = (deltaX: number, deltaY: number): SwipeDirection => {
const absX = Math.abs(deltaX);
const absY = Math.abs(deltaY);
if (absX > absY) {
return deltaX > 0 ? SwipeDirection.RIGHT : SwipeDirection.LEFT;
} else {
return deltaY > 0 ? SwipeDirection.DOWN : SwipeDirection.UP;
}
};
3.2 方向检测数学原理
使用向量分析确定主要滑动方向:
scss
设滑动向量为 V = (deltaX, deltaY)
主方向判断:
if |deltaX| > |deltaY| then 水平滑动
else 垂直滑动
具体方向:
水平:deltaX > 0 → RIGHT, deltaX < 0 → LEFT
垂直:deltaY > 0 → DOWN, deltaY < 0 → UP
4. 阻尼系统算法
4.1 阻尼计算公式
typescript
const dampedDeltaY = deltaY * dampingFactor;
const newTranslateY = initialOffset + dampedDeltaY;
4.2 边界阻尼算法
当滑动超出边界时,应用额外的阻尼效果:
typescript
if (newTranslateY > maxY) {
const overscroll = newTranslateY - maxY;
finalTranslateY = maxY + overscroll * 0.3; // 30%的边界阻尼
} else if (newTranslateY < minY) {
const overscroll = minY - newTranslateY;
finalTranslateY = minY - overscroll * 0.3;
}
4.3 阻尼系统数学模型
graph LR
A["原始位移 Δy"] --> B["基础阻尼"]
B --> C["dampedΔy = Δy × dampingFactor"]
C --> D{"边界检查"}
D -->|"超出上边界"| E["上边界阻尼"]
D -->|"超出下边界"| F["下边界阻尼"]
D -->|"在范围内"| G["直接应用"]
E --> H["finalY = maxY + overscroll × 0.3"]
F --> I["finalY = minY - overscroll × 0.3"]
G --> J["finalY = dampedΔy"]
style A fill:#3498db,stroke:#2980b9,color:#fff
style C fill:#9b59b6,stroke:#8e44ad,color:#fff
style H fill:#e74c3c,stroke:#c0392b,color:#fff
style I fill:#e74c3c,stroke:#c0392b,color:#fff
style J fill:#27ae60,stroke:#229954,color:#fff
5. 智能吸附算法
5.1 吸附判断逻辑
typescript
const shouldSwitch = (deltaY: number, velocityY: number, containerHeight: number) => {
const threshold = containerHeight * minSwipeThreshold;
const isFastSwipe = Math.abs(velocityY) > fastSwipeThreshold;
return Math.abs(deltaY) > threshold || isFastSwipe;
};
5.2 智能吸附数学公式
吸附决策基于两个条件的逻辑或运算:
ini
条件1:距离阈值判断
|deltaY| > containerHeight × minSwipeThreshold
条件2:速度阈值判断
|velocityY| > fastSwipeThreshold
最终判断:
shouldSwitch = 条件1 OR 条件2
5.3 目标索引计算
typescript
if (deltaY > 0) {
// 向下滑动 - 显示上一个模板
targetIndex = Math.max(0, currentIndex - 1);
if (loop && currentIndex === 0) {
targetIndex = items.length - 1;
}
} else {
// 向上滑动 - 显示下一个模板
targetIndex = Math.min(items.length - 1, currentIndex + 1);
if (loop && currentIndex === items.length - 1) {
targetIndex = 0;
}
}
6. 动画系统算法
6.1 贝塞尔曲线动画
使用 CSS 贝塞尔曲线实现平滑动画:
css
transition: transform 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94)
6.2 动画状态机
stateDiagram-v2
[*] --> Idle
Idle --> Dragging : touchStart
Dragging --> Animating : touchEnd
Animating --> Idle : animationComplete
Dragging --> Dragging : touchMove
state Animating {
[*] --> SwitchAnimation
[*] --> BounceAnimation
[*] --> CloseAnimation
SwitchAnimation --> [*]
BounceAnimation --> [*]
CloseAnimation --> [*]
}
6.3 变换矩阵计算
垂直滑动的变换计算:
typescript
// 基础变换:将当前项目显示在视口顶部
const baseTransform = -containerHeight; // 向上偏移一个容器高度
// 最终变换:基础变换 + 滑动偏移
const finalTransform = baseTransform + translateY;
// CSS 变换
style.transform = `translateY(${finalTransform}px)`;
7. 滑动关闭算法 (useSwipeToClose)
7.1 关闭进度计算
typescript
const progress = Math.abs(deltaX) / containerWidth;
7.2 智能关闭判断
typescript
const shouldClose = (deltaX: number, velocity: number, progress: number) => {
if (Math.abs(velocity) > fastSwipeThreshold) {
return velocity > 0; // 快速右滑
} else {
return progress >= snapThreshold; // 慢速滑动基于进度
}
};
7.3 关闭动画算法
flowchart TD
A["检测右滑手势"] --> B["计算滑动进度"]
B --> C{"velocity > threshold?"}
C -->|"是"| D["快速滑动判断"]
C -->|"否"| E["进度判断"]
D --> F{"velocity > 0?"}
F -->|"是"| G["执行关闭"]
F -->|"否"| H["执行回弹"]
E --> I{"progress ≥ snapThreshold?"}
I -->|"是"| G
I -->|"否"| H
G --> J["动画到屏幕外"]
H --> K["动画回原位"]
J --> L["触发 onClose"]
K --> M["重置状态"]
style A fill:#3498db,stroke:#2980b9,color:#fff
style G fill:#e74c3c,stroke:#c0392b,color:#fff
style H fill:#f39c12,stroke:#d68910,color:#fff
style L fill:#27ae60,stroke:#229954,color:#fff
style M fill:#27ae60,stroke:#229954,color:#fff
8. 性能优化算法
8.1 可见项目计算
只渲染当前项目及其前后各一个项目:
typescript
const getVisibleItems = () => {
const visibleItems = [];
const safeIndex = Math.max(0, Math.min(currentIndex, items.length - 1));
for (let i = safeIndex - 1; i <= safeIndex + 1; i++) {
let actualIndex = i;
let item = null;
if (loop) {
// 循环模式的索引计算
if (i < 0) actualIndex = items.length + i;
else if (i >= items.length) actualIndex = i - items.length;
}
if (actualIndex >= 0 && actualIndex < items.length) {
item = items[actualIndex];
}
visibleItems.push({ item, index: actualIndex });
}
return visibleItems;
};
8.2 内存优化策略
graph TD
A["总项目数:N"] --> B["渲染项目数:3"]
B --> C["内存节省:(N-3)/N × 100%"]
D["示例:100个项目"] --> E["只渲染3个"]
E --> F["节省97%内存"]
G["动态更新"] --> H["滑动时重新计算"]
H --> I["始终保持3个项目"]
style A fill:#3498db,stroke:#2980b9,color:#fff
style C fill:#27ae60,stroke:#229954,color:#fff
style F fill:#e74c3c,stroke:#c0392b,color:#fff
style I fill:#9b59b6,stroke:#8e44ad,color:#fff
关键数学公式总结
1. 速度计算
css
velocity = Δposition / Δtime
2. 阻尼计算
ini
dampedOffset = originalOffset × dampingFactor
3. 边界阻尼
ini
finalOffset = boundaryValue + overscroll × boundaryDamping
4. 吸附判断
ini
shouldSwitch = (|Δy| > threshold) OR (|velocity| > speedThreshold)
5. 进度计算
ini
progress = |displacement| / containerDimension
6. 变换矩阵
ini
transform = baseOffset + dynamicOffset
配置参数说明
参数 | 类型 | 默认值 | 说明 | 数学意义 |
---|---|---|---|---|
minSwipeThreshold |
number | 0.15 | 触发切换的最小距离比例 | threshold = containerHeight × 0.15 |
fastSwipeThreshold |
number | 0.5 | 快速滑动速度阈值 (px/ms) | velocity > 0.5 px/ms |
dampingFactor |
number | 0.8 | 阻尼系数 (0-1) | dampedOffset = offset × 0.8 |
animationDuration |
number | 300 | 动画持续时间 (ms) | CSS transition duration |
edgeDetectionWidth |
number | 120 | 边缘检测宽度 (px) | edgeZone = 120px |
snapThreshold |
number | 0.4 | 智能吸附阈值 (0-1) | progress ≥ 0.4 |
minTouchDuration |
number | 100 | 最小触摸时间 (ms) | duration ≥ 100ms |
使用示例
基础垂直滑动
tsx
const templates = [
{ id: 1, content: <TemplateA /> },
{ id: 2, content: <TemplateB /> },
{ id: 3, content: <TemplateC /> },
];
<SwipeMultiContainer
items={templates}
currentIndex={currentIndex}
onItemChange={(index, item) => setCurrentIndex(index)}
minSwipeThreshold={0.15} // 15% 容器高度触发切换
dampingFactor={0.8} // 80% 跟手度
fastSwipeThreshold={0.5} // 0.5px/ms 快速滑动
/>
启用边缘滑动关闭
tsx
<SwipeMultiContainer
items={templates}
currentIndex={currentIndex}
onItemChange={(index, item) => setCurrentIndex(index)}
enableSwipeToClose={true}
onClose={() => closeDialog()}
edgeDetectionWidth={120} // 120px 边缘检测区域
snapThreshold={0.4} // 40% 进度触发关闭
/>
总结
SwipeMultiContainer
通过精心设计的算法系统,实现了流畅、智能的滑动交互体验。其核心算法包括:
- 触摸事件处理:实时计算速度和位移
- 边缘检测:精确识别滑动起始位置
- 方向判断:基于向量分析确定滑动方向
- 阻尼系统:提供自然的物理感知
- 智能吸附:结合距离和速度的双重判断
- 动画系统:平滑的贝塞尔曲线过渡
- 性能优化:最小化内存占用和渲染开销
这些算法的协同工作,确保了组件在各种使用场景下都能提供优秀的用户体验。