View Timeline:元素进出视口的进度
本页建立在 Scroll Timeline 的 animation-timeline 概念之上。scroll-timeline 看的是滚动条进度;view-timeline 看的是某个元素相对视口的位置——从它「即将进入视口」到「完全离开视口」映射成 0% 到 100%,是做「滚到此处元素淡入 /
飞入」最直接的方式。属性语义和 scroll-timeline 基本一致(定义名称 + 方向)。浏览器支持情况见 caniuse · view-timeline。
1 · 名称、方向与 inset
| 属性 | 作用 |
|---|---|
view-timeline-name / view-timeline-axis |
同 scroll-timeline:命名 + 方向。简写 view-timeline: --name axis。 |
view-timeline-inset |
类似 scroll-padding,内缩 / 外扩视口的判定区间,改变 0% / 100% 的触发点。 |
view() |
可理解为 view-timeline-axis + view-timeline-inset 的匿名简写。 |
除了把 view-timeline 接到 animation-timeline,@keyframes 里还能直接写 基于 range 的关键帧——见下文「@keyframes 里的 range 关键帧」。
2 · animation-range 的关键字(view-timeline 专属)
关键提醒:subject 尺寸可能小于 / 等于 / 大于 viewport,同一个 range 关键字在不同尺寸下的含义随之不同。
| 关键字 | 含义 |
|---|---|
cover |
subject 与 viewport 有交集的整段。 |
contain |
subject 完全包含 viewport、或完全被 viewport 包含的那段。 |
entry / entry-crossing |
进入阶段(subject 小于 viewport 时,entry 100% = 完全进入)。 |
exit / exit-crossing |
离开阶段。 |
各 range 的图示可在 view-timeline ranges 工具 与 WebKit cheatsheet 看到。
3 · 跟着元素进出视口读进度
下面 subject 块放在一个可滚动容器里,滚动让它穿过容器视口。顶部绿条用 animation-timeline: --view 把宽度从 0% 跑到 100%,再用 @property 注册成整型的自定义属性配合 CSS counter() 把进度显示成数字——全程没有 JS 监听 scroll。进度条是 subject
的前置兄弟,本无法引用后面才声明的时间线,靠共同祖先上的 timeline-scope: --view 把这条 timeline 提升作用域,进度条才引用得到。
拖 view-timeline-inset 滑块等于给视口判定区间内缩 / 外扩,0% / 100% 的触发点随之移动;切换 subject 高度可体会「subject 小于 / 大于 viewport」时进度推进节奏的差异。
4 · @keyframes 里的 range 关键帧
用了 view-timeline 后,@keyframes 的关键帧选择器除了 0% 到 100%,还能直接写 timeline range 名字:entry 0% / entry 100% / exit 0% / exit 100% 等。于是「进入阶段做什么、离开阶段做什么」可以写进同一组
@keyframes,而不用去算笼统的百分比。
@keyframes reveal {
entry 0% { opacity: 0; transform: translateY(40px) scale(.85); }
entry 100% { opacity: 1; transform: none; } /* 完全进入视口时到位 */
exit 0% { opacity: 1; transform: none; }
exit 100% { opacity: 0; transform: translateY(-40px) scale(.85); }
}
.box { view-timeline: --b; animation-timeline: --b; animation: reveal both; }
下面的卡片就是这么写的(它自己既是 subject,又消费自己的 view-timeline)。慢慢滚,看它在 entry 段升入、exit 段淡出:
关键帧钉在 range 上,语义比百分比清楚得多:entry 100% 就是「刚好完全进入视口」那一刻,无需关心元素与视口的相对尺寸。range 名字与上面 animation-range 的关键字一致(entry / exit / cover / contain)。
5 · 缓动照常生效:用 linear() 做弹簧入场
view / scroll-driven 只是把时间轴换成滚动进度,timing-function 照常工作。把入场段的缓动换成多段 linear() 弹簧曲线(可用
linear() 生成器 造),元素滚进视口时就会过冲再回弹——纯 CSS、不用任何 JS spring 库。用 animation-range: cover 0% cover 45% 把入场压在前 45% 行程里,弹簧才有足够滚动距离看清。
这里用 animation-fill-mode: backwards(不是前一个 lab 的 both):元素在进入动画区间之前就该停在屏外起始态(@keyframes 的 0% 帧:translateY /
scale),否则它会先以元素本来的样式显示、滚到区间时再突然跳到起始态闪一下。取消勾选上面的 backwards 开关即可复现这次闪跳。凡是「初始在视口外、滚进来才动」的元素,都得靠 backwards 把起始帧前置。