苹果式滚动动画 是做什么的?
效果由滚动位置控制,而非由计时器触发。向上滚动会反向播放动画。无需库。
三个核心思想
- 固定。一个高的容器(例如
height: 300vh)包含一个position: sticky; top: 0; height: 100vh; overflow: hidden的子元素。该子元素在容器滚动时保持固定在屏幕上。容器额外的vh就是你的动画预算。 - 进度。一个函数将滚动位置转换为 0..1 之间的数字:
- 固定区块:
clamp(-rect.top / (rect.height - innerHeight)) - 元素进入视口:
clamp((innerHeight*a - rect.top) / (innerHeight*(a-b)))
- 固定区块:
- 用进度驱动视觉。每帧设置
translate、rotate、scale、opacity(以及borderRadius)。一个使用requestAnimationFrame节流的scroll监听器重新计算所有效果。
即用即贴引擎:references/scroll-engine.js。
效果很好的方案(选 3-5 个,不要全用)
| 效果 | 如何实现 |
|---|---|
| 组装 | 元素从视口边缘散落(随机角度、旋转、缩放 .35、透明度 .2)逐渐过渡到其布局位置。在加载/调整大小时计算出每个元素到其自然位置的偏移量,然后乘以 (1 - ease(p))。按索引交错。 |
| 逐字点亮 | 将每个词包裹在 span 中。透明度 .18 → 1,使用 clamp(p * 1.25 * N - k)。 |
| 固定步骤 | step = floor(p * N);分数部分 p*N - step 填充当前步骤内部的进度条。点击步骤应 scrollTo 该步骤的位置。 |
| 倾斜展平 | perspective(1400px) rotateX((1-e)*22deg) translateY((1-e)*60px) scale(.84 + .16*e) 用于手机/设备模型。 |
| 卡片升起 | translate: 0 (1-e)*80px; scale: .9+.1e; opacity: e,按列延迟 (i % cols)。 |
| 展开 | 视频/图片 scale .78 → 1 且 borderRadius 44px → 28px。 |
在应用进度之前使用 ease = t => 1 - (1-t)**3。
规则(经验教训)
- 使用独立的 CSS 属性
translate、rotate、scale进行进度控制,将transform留给悬停。否则悬停和滚动会争夺同一个属性。 - 不要在同一元素上结合
.reveal渐变类与进度控制的变换。选择一种。 - 将区块标题放在固定面板内与内容在一起。如果面板仅包含内容且居中,高显示器会出现大片空白。
- 保持滚动距离适中。对于 N 步步骤,约为
N * 55vh + 100vh;对于一次性效果,为240-300vh。用户会觉得 700vh+ 很累。 - 限制固定内容不超过 100vh。在短屏幕(
max-height: 820px)和手机上,隐藏副标题和额外内容,步骤中只显示当前步骤。 - 减少动画偏好:如果
prefers-reduced-motion: reduce,则返回进度1(最终状态,无动画)。不要隐藏元素。 - 祖先元素上 sticky 不需要设置
overflow,除了body上的overflow-x: hidden,这是可以的。避免在 sticky 元素和页面之间的任何容器上设置overflow: hidden。 - 调整大小时重新计算(组装的偏移量取决于布局)。
- 仅对
translate/rotate/scale/opacity/border-radius做动画;避免使用布局属性。 - 避免微小的视频首尾段:如果使用
<video>播放进行进度控制,短片段可能渲染为空白。
典型页面顺序
英雄区(固定、组装) → 一句普通陈述(无效果) → 验证/电影(展开) → 固定步骤 → 设备模型(倾斜展平) → 卡片网格(卡片升起) → 行动号召。在黑色和浅灰色区块之间交替以营造苹果感觉;使用一种强调色。
测试
无头 Chrome 的 --screenshot 无法滚动。需通过开发者工具协议驱动真实 Chrome:
chrome --headless=new --remote-debugging-port=9333 --user-data-dir=/tmp/prof about:blank &- 从 Node(v22+ 支持全局
WebSocket):获取http://localhost:9333/json,打开页面的webSocketDebuggerUrl,发送Emulation.setDeviceMetricsOverride、Page.navigate,然后对每个位置执行Runtime.evaluate("scrollTo(0,Y)"),等待约 700 毫秒,Page.captureScreenshot。 - 从各区块的
offsetTop和高度中选择 Y(例如,每个固定区块的 0%、60%、100%)。 - 在桌面端 (1440x900)、大屏幕 (2000x1300) 和手机端 (390x800) 检查,并确认
document.documentElement.scrollWidth === innerWidth(无水平溢出)。
交付检查清单
- 每个效果由滚动进度驱动,可逆
- 减少动画偏好降级
- 检查手机布局,无水平滚动
- 固定滚动长度适中;高屏幕无空白
- 在每个固定区块的开始/中间/结束处截图