动画进度控制 ( timeline )
虽然实作 CSS 动画通常都需要手动设定“进度百分比”,但目前已有部分浏览器支援“视窗可视位置”以及“浏览器卷轴”控制动画进度的功能,这篇教学会介绍运用 CSS 的 animation-timeline、view-timeline 和 animation-range 样式属性,实现进阶的动画进度控制功能。
- 注意,目前部分浏览器不支援这些样式属性 ( Firefox、Safari、IE )!
- 建议先阅读:CSS 动画 animation
快速导览:
animation-timeline 动画进度控制 ( 动画时间轴 )
animation-timeline 是一个可以“根据画面或卷轴控制动画进度”的样式属性,会将元素在“可视范围”里的位置,或元素所在阶层的“视窗卷轴”,当作“时间轴 timeline”,时间轴对应到动画所设定的进度百分比,就够单纯透过 CSS 读取这些元素之外数值,在不需要 JavaScript 的辅助下,控制动画进度,这个样式没有继承特性,具有下列属性值:
注意,
animation-timeline在 CSS 中的顺序必须摆在animation-name之后。
| 属性值 | 说明 |
|---|---|
| none | 预动画进度与时间轴无关 ( 默认值 )。 |
| auto | 动画进度自动套用文件时间轴 ( Document.timeline )。 |
| view() | 动画进度使用元素在“可视范围”中的位置作为时间轴。 |
| scroll() | 动画进度使用画面卷轴作为时间轴。 |
animation-timeline: view() 画面高度
animation-timeline 属性值里的 view() 属于 CSS 函数,支援下列参数数值:
| 参数 | 说明 |
|---|---|
| block | 逻辑参数,表示垂直方向 ( 默认值,等同 0% 0% )。 |
| inline | 逻辑参数,表示水平方向。 |
| y | 垂直方向 |
| x | 水平方向 |
| start | 垂直方向上方 ( 等同 start 0% ) |
| start end | 垂直方向上方、垂直方向下方 |
| block start | 垂直方向上方、垂直方向下方 ( 等同 start 0% ) |
| block start end | 垂直方向上方、垂直方向下方 |
| inline start | 左向右书写模式下,水平方向左侧、水平方向右侧 ( 等同 inline start 0% ) |
| inline start end | 左向右书写模式下,水平方向左侧、水平方向右侧 |
下方的范例会展示使用 view() 属性值,搭配各种不同参数所呈现的效果,当用户卷动卷轴时,会看见内容方块出现变色、尺寸改变的效果,可以注意设定了 start 和 end 的元素,会在指定的位置才发生改变,位置外的样貌会呈现原本元素的样式属性。
<!-- HTML 程式碼 -->
垂直方向改變動畫進度
<div class="out a">
<div>往下拉,觀察變化狀況</div><div class="in"></div><div></div>
</div>
垂直方向改變動畫進度 ( 限制上 50px 下 100px )
<div class="out b">
<div>往下拉,觀察變化狀況</div><div class="in"></div><div></div>
</div>
水平方向改變動畫進度
<div class="out c">
<div>往右拉,觀察變化狀況</div><div class="in"></div><div></div>
</div>
水平方向改變動畫進度 ( 限制左 50px 右 100px )
<div class="out d">
<div>往右拉,觀察變化狀況</div><div class="in"></div><div></div>
</div>
<!-- CSS 程式碼 -->
<style>
.out {
width: 300px;
height: 200px;
overflow: scroll; /* 隱藏超出範圍的子元素並出現捲軸 */
margin: 5px 0 20px 0 ;
border: 1px solid #000;
}
/* 使用一般的 div 撐高畫面,並用漸層背景讓效果更明顯 */
div:not([class]) {
width: 400px;
height: 300px;
background: repeating-linear-gradient(45deg,#fff 0%, #fff 5%,#eee 5%, #eee 10%);
}
/* 水平捲動的一般 div 高度較小,讓變化的 div 一開始就出現 */
.c div:not([class]), .d div:not([class]) {height: 100px;}
/* 動畫進度會改變的 div,注意只使用了動畫名稱而已 */
.in {
width: 100px;
height: 100px;
background: red;
animation-name: oxxo; /* 只使用動畫名稱 */
}
/* 動畫設定 */
@keyframes oxxo {
0% {background: red;}
25% {background: yellow;}
50% {background: green;}
75% {background: blue;}
100% {
background: black;
width: 20px;
height: 20px;
}
}
.a .in {animation-timeline: view();} /* 元素在顯示畫面中垂直方向的位置,等同 view(0% 0%) */
.b .in {animation-timeline: view(50px 100px);} /* 元素在顯示畫面中垂直方向的位置 */
.c .in {
margin: 0 300px; /* 讓畫面出現水平捲軸 */
animation-timeline: view(inline); /* 元素在顯示畫面中水平方向的位置,等同 view(inline 0% 0%) */
}
.d .in {
margin: 0 300px; /* 讓畫面出現水平捲軸 */
animation-timeline: view(inline 50px 100px ); /* 元素在顯示畫面中水平方向的位置 */
}
</style>
animation-timeline: scroll() 卷轴位置
animation-timeline 属性值里的 scroll() 属于 CSS 函数,支援下列参数数值:
注意,
animation-timeline在 CSS 中的顺序必须摆在animation-name之后。
| 参数 | 说明 |
|---|---|
| block | 逻辑参数,表示“最靠近元素且具有卷轴的父层元素”的垂直方向卷轴 ( 默认值,等同 block nearest )。 |
| inline | 逻辑参数,表示“最靠近元素且具有卷轴的父层元素”的水平方向卷轴 ( 等同 inline nearest )。 |
| nearest | 搭配参数,表示“最靠近元素且具有卷轴的父层元素”的卷轴。 |
| root | 搭配参数,“页面本身的卷轴。 |
| self | 搭配参数,元素本身的卷轴。 |
下方的范例会展示使用 scroll() 属性值,搭配各种不同参数所呈现的效果,第一组元素会在卷动最靠近元素的父层元素卷轴时发生变化,第二组则是会在卷动页面卷轴时发生变化,第三组会在卷动元素本身卷轴时发生变化。
<!-- HTML 程式碼 -->
最靠近元素的父層元素捲軸
<div class="out a">
<div>往下拉,觀察變化狀況</div>
<div class="in">apple<br>banana<br>oxxo<br>coconut<br>papaya</div>
<div></div>
</div>
網頁文件捲軸
<div class="out b">
<div>往下拉,觀察變化狀況</div>
<div class="in">apple<br>banana<br>oxxo<br>coconut<br>papaya</div>
<div></div>
</div>
元素本身捲軸
<div class="out c">
<div>往右拉,觀察變化狀況</div>
<div class="in">apple<br>banana<br>oxxo<br>coconut<br>papaya</div>
<div></div>
</div>
<br><br><br><br><br><br><br><br><br><br><br><br>
<!-- CSS 程式碼 -->
<style>
.out {
width: 300px;
height: 150px;
overflow: scroll; /* 隱藏超出範圍的子元素並出現捲軸 */
margin: 5px 0 20px 0 ;
border: 1px solid #000;
}
/* 使用一般的 div 撐高畫面,並用漸層背景讓效果更明顯 */
div:not([class]) {
background: repeating-linear-gradient(45deg,#fff 0%, #fff 5%,#eee 5%, #eee 10%);
width: 400px;
height: 50px;
}
/* 撐高產生捲軸 */
div:nth-child(3){height: 150px;}
/* 動畫進度會改變的 div,注意只使用了動畫名稱而已 */
.in {
width: 100px;
height: 100px;
animation-name: oxxo; /* 動畫名稱 */
overflow: scroll; /* 元素本身產生捲軸 */
}
/* 動畫設定 */
@keyframes oxxo {
0% {background: red;}
25% {background: yellow;}
50% {background: green;}
75% {background: blue;}
100% {
background: black;
width: 20px;
height: 20px;
}
}
.a .in {animation-timeline: scroll();} /* 捲動最靠近元素的父層元素捲軸 */
.b .in {animation-timeline: scroll(root);} /* 捲動頁面捲軸 */
.c .in {animation-timeline: scroll(self);} /* 捲動元素本身捲軸 */
</style>
view-timeline 可视范围时间轴
view-timeline 是针对 animation-timeline 属性值为 view() 的延伸样式属性,可以设定一组类似 CSS 变数的名称与数值,接着只要呼叫这个名称,就能执行和 view() 完全相同的效果,这个样式是 view-timeline-name、view-timeline-axis 和 view-inset 的缩写格式,这些样式属性都没有继承特性,通常只使用 view-timeline 或单纯透过 view() 来实现效果。
下方范例会将 animation-timeline: view() 的写法换成 view-timeline,会呈现完全相同的效果,当元素离底部 100px 时就会开始变色变小,值到离顶部 50px 为止。
<!-- HTML 程式碼 -->
animation-timeline: view(50px 100px);
<div class="out a">
<div>往下拉,觀察變化狀況</div><div class="in"></div><div></div>
</div>
view-timeline: --a 50px 100px;<br>
animation-timeline: --a;
<div class="out b">
<div>往下拉,觀察變化狀況</div><div class="in"></div><div></div>
</div>
<!-- CSS 程式碼 -->
<style>
.out {
width: 300px;
height: 200px;
overflow: scroll; /* 隱藏超出範圍的子元素並出現捲軸 */
margin: 5px 0 20px 0 ;
border: 1px solid #000;
}
/* 使用一般的 div 撐高畫面,並用漸層背景讓效果更明顯 */
div:not([class]) {
width: 400px;
height: 300px;
background: repeating-linear-gradient(45deg,#fff 0%, #fff 5%,#eee 5%, #eee 10%);
}
/* 動畫進度會改變的 div,注意只使用了動畫名稱而已 */
.in {
width: 100px;
height: 100px;
background: red;
animation-name: oxxo; /* 只使用動畫名稱 */
}
/* 動畫設定 */
@keyframes oxxo {
0% {background: red;}
100% {
background: black;
width: 20px;
height: 20px;
}
}
.a .in {
view-timeline: --a 50px 100px; /* 建立名稱和數值 */
animation-timeline: --a; /* 讀取名稱 */
}
.b .in {animation-timeline: view(50px 100px);}
</style>
animation-range 动画执行范围
animation-range 表示“动画执行范围”的样式属性,可以针对 view() 或 scroll() 进行数值范围设定,这个样式是 animation-range-start 和 animation-range-end 的缩写格式,这些样式属性都没有继承特性,通常只使用 animation-range 来实现效果。
特别注意
animation-range使用的数值和view()与scroll()不同,animation-range的数值表示“元素顶部距离出现位置的距离”。
下方会使用 animation-range 代替 view() 的设定,做出更容易理解位置的效果,和下方的另外一组对照组,呈现完全相同的效果。
<!-- HTML 程式碼 -->
animation-timeline: view();<br>
animation-range: 50px 150px;
<div class="out a">
<div>往下拉,觀察變化狀況</div><div class="in"></div><div></div>
</div>
animation-timeline: view(50px 50px);
<div class="out b">
<div>往下拉,觀察變化狀況</div><div class="in"></div><div></div>
</div>
<!-- CSS 程式碼 -->
<style>
.out {
width: 300px;
height: 200px;
overflow: scroll; /* 隱藏超出範圍的子元素並出現捲軸 */
margin: 5px 0 20px 0 ;
border: 1px solid #000;
}
/* 使用一般的 div 撐高畫面,並用漸層背景讓效果更明顯 */
div:not([class]) {
width: 400px;
height: 300px;
background: repeating-linear-gradient(45deg,#fff 0%, #fff 5%,#eee 5%, #eee 10%);
}
/* 動畫進度會改變的 div,注意只使用了動畫名稱而已 */
.in {
width: 100px;
height: 100px;
background: red;
animation-name: oxxo; /* 只使用動畫名稱 */
}
/* 動畫設定 */
@keyframes oxxo {
0% {background: red;}
100% {
background: black;
width: 20px;
height: 20px;
}
}
.a .in {
animation-timeline: view(); /* 使用可視範圍控制動畫進度 */
animation-range: 50px 150px; /* 元素頂部為 50px 開始動畫,值到距離 150px 為止 */
}
.b .in {
animation-timeline: view(50px 50px); /* 出現時 50px,結束值為 300px-250px */
}
</style>
对于 view() 而言,animation-range 也可以将上述的数值,搭配下方的属性值,实现更进阶的设定。
| 属性值 | 说明 |
|---|---|
| normal | 默认值,按照上述数值控制元素动画进度。 |
| contain | 元素完全被包含在可视范围中。 |
| cover | 元素在可视范围进入到完全退出的过程。 |
| exit | 元素开始退出可视范围。 |
| exit-crossing | 元素开始穿越可视范围结束边缘 ( 类似 exit )。 |
| entry | 元素开始进入可视范围。 |
| entry-crossing | 元素开始穿越可视范围起始边缘 ( 类似 entry )。 |
下方会使用六个 div,从左到右分别是使用 contain、cover、exit、exit-crossing、entry 和 entry-crossing 所呈现的效果,拖拉卷轴后,可以观察元素进入和离开可视范围时动画进度的表现。
<!-- HTML 程式碼 -->
<div class="out">
<div></div>
<div class="in a"></div>
<div class="in b"></div>
<div class="in c"></div>
<div class="in d"></div>
<div class="in e"></div>
<div class="in f"></div>
<div></div>
</div>
<!-- CSS 程式碼 -->
<style>
.out {
width: 350px;
height:250px;
overflow: scroll; /* 隱藏超出範圍的子元素並出現捲軸 */
margin: 5px 0 20px 0 ;
padding: 20px;
border: 1px solid #000;
background: repeating-linear-gradient(45deg,#fff 0, #fff 25px,#eee 25px, #eee 50px);
background-attachment: local;
}
/* 使用一般的 div 撐高畫面,並用漸層背景讓效果更明顯 */
div:not([class]) {
width: 400px;
height: 300px;
clear: both;
}
/* 動畫進度會改變的 div,注意只使用了動畫名稱而已 */
.in {
width: 40px;
height: 100px;
margin: 5px;
float: left;
background: red;
animation-name: oxxo; /* 動畫名稱 */
animation-fill-mode: forwards; /* 動畫結束後停留在最後一格 */
animation-timeline: view(); /* 使用可視範圍控制動畫進度 */
}
/* 動畫設定 */
@keyframes oxxo {
0% {background: red;}
100% {
background: black;
height: 200px;
}
}
.a {animation-range: contain;}
.b {animation-range: cover;}
.c {animation-range: exit;}
.d {animation-range: exit-crossing;}
.e {animation-range: entry;}
.f {animation-range: entry-crossing;}
</style>
因为 animation-scroll 无法读取卷轴移动距离,而透过 animation-range 就能读取卷轴卷动的数值,透过下拉卷轴的距离控制动画进度,下方范例会将三个 div 套用不同的 animation-range,执行后将卷轴向下拉动,可以发现三个 div 会在不同高度卷轴时开始执行动画进度,产生有趣的视觉效果。
<!-- HTML 程式碼 -->
<div class="out">
<div class="in a"></div>
<div class="in b"></div>
<div class="in c"></div>
<div></div>
</div>
<!-- CSS 程式碼 -->
<style>
.out {
width: 350px;
height:250px;
overflow: scroll; /* 隱藏超出範圍的子元素並出現捲軸 */
padding: 20px;
border: 1px solid #000;
background: repeating-linear-gradient(45deg,#fff 0, #fff 25px,#eee 25px, #eee 50px); /* 用漸層背景讓效果更明顯 */
background-attachment: local; /* 固定背景 */
}
div:not([class]) {
width: 400px;
height: 1000px;
clear: both;
}
.in {
width: 100px;
height: 100px;
margin: 5px;
float: left;
background: red;
animation-name: oxxo; /* 動畫名稱 */
animation-fill-mode: forwards; /* 動畫結束後停留在最後一格 */
animation-timeline: scroll(); /* 根據捲軸播放動畫進度 */
}
/* 動畫設定 */
@keyframes oxxo {
0% {background: red;}
100% {
background: black;
height: 300px;
}
}
.a {animation-range: 10px 100px;} /* 動畫範圍是捲軸移動的 10px~100px */
.b {animation-range: 50px 150px;} /* 動畫範圍是捲軸移動的 50px~150px */
.c {animation-range: 100px 200px;} /* 動畫範圍是捲軸移動的 100px~200px */
</style>
小结
一但可以透过元素在“可视范围的位置”或“卷轴位置”控制动画进度,就更能大幅发挥 CSS 强大的动画能力,甚至不需要 JavaScript 就能做出许多有趣的卷轴效果,可惜目前还有部分浏览器不支援,不过相信这种好用的功能,不久的未来应该会都支援啰。
延伸阅读:
微信扫码关注
抖音扫码关注