CSS关键帧动画由 @keyframes 创建一个自定义名称的动画序列,再由元素对应的选择器通过 animation 属性调用动画。
一、创建CSS关键帧动画
创建CSS关键帧动画用 @keyframes 实现,语法结构:
@keyframes 名称 {
时间节点1 { CSS属性 }
时间节点2 { CSS属性 }
时间节点N { CSS属性 }
}
@keyframes 创建的动画序列以一系列关键帧描述动画形态,包含时间节点和动画数据。时间节点可以使用百分比或关键字 from 和 to 表示,动画数据则是任意支持关键帧动画的单个或多个CSS属性。试看:
/* 方法一 :百分比 */
@keyframes move {
0% { left: 0px; top: 10px; }
100% { left: 240px; top: 180px; }
}
/* 方法二 :关键字 */
@keyframes move {
from { left: 0px; top: 10px; }
to { left: 240px; top: 180px; }
}
上述两种关键帧动画的设计等价,但方法一更易于扩展:在0%~100% 之间可以加入任意多个时间节点用以描述更多的关键帧。方法二适用于简易动画,它只关心两个时间节点即 0% 和 100% 的动画状态。
很多时候元素用于关键帧动画的对应CSS属性已经设置了初始值,若此,关键帧动画开始的时间节点可以省略(特别是基于关键字方法):
/* 元素定义了 left和 top 初始值 */
.ball { position: absolute; left: 0px; top: 10px; }
/* keyframes 关键帧动画可以省略 from 关键字属性 */
@keyframes move {
to { left: 240px; top: 180px; }
}
对于追求简洁代码需求而言,这是有效的方法,但一切应根据具体情况而定。
二、运行CSS关键帧动画
CSS关键帧动画由元素运行,通过元素对应CSS选择器的 animation 属性进行设置。animation 属性语法结构如下:
/* 红色参数必须,其余可选(缺省时使用默认值) */
animation: ①动画名称 ②周期时长 ③时间函数 ④延时时间 ⑤运行次数 ⑥运动方向 ⑦结束状态 ⑧播放状态
/* 应用举例 :①move动画,②6秒一周期,③匀速,④延时0秒,⑤运行一次,⑥正向,⑦保留最后状态,⑧运行 */
animation: move 6s linear 0s 1 normal forwards running;
/* 因为 0s、normal、running 属性值都是默认值,可以简化为 */
animation: move 6s linear 1 forwards;
animation 是一个简写属性,它包含多个 animation-* 属性:
- animation-name :由 @keyframes 创建的动画名称,支持单个、多个
- animation-duration :动画周期时长,即完成一个动画周期所需的时间,时间单位
- animation-timing-function : 时间函数,即动画在每个周期的持续时间内如何进行,例如匀速、先快后慢、两头慢中间快或自定义运动时间曲线等等(文末附详细列表)
- animation-delay :动画延时时间,本质指动画执行之前等待的时间量,支持正(延后)负(提前)数,时间单位
- animation-iteration-count :动画运行次数,小于等于0表示不运行,infinite 表示永久运行
- animation-direction :运动方向,即播放动画的方向与 @keyframes 时间节点指向的关系,值有 normal(默认,正向)、reverse(反向)、alternate(往复)、alternate-reverse(反向往复)
- animation-fill-mode :动画结束时的状态,确切以言之,该属性设置 CSS 动画在执行之前和之后如何将样式应用于其目标。默认值是 none,可选值 forwards(保留最后帧状态)、backwards(首帧状态)
- animation-play-state :动画播放状态,设置动画播放(running)或暂停(paused)
属性较多,可以根据需要取舍,逐个设置时顺序不论:
.ball {
/* ... 这里是其它代码 */
animation-name: move;
animation-duration: 6s;
animation-timing-function: linear;
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
建议尽可能使用 animation 简写属性设置动画。
三、动画实例
(一)元素平移
<style>
.rect {
width: 40px;
height: 40px;
background: cyan;
transform: translate(0,100px);
/* 匀速、8秒一个周期、永动、往复(来、回各算一个周期) */
animation: move linear 8s infinite alternate;
}
/* 动画 :水平移动 */
@keyframes move {
to { transform: translate(800px,100px); }
}
</style>
<div class="rect"></div>
(二)元素旋转
<style>
.rect {
margin: 200px;
width: 180px;
height: 180px;
background: lightgreen;
/* 慢-快-慢、5秒一个周期、2秒后开始、转3圈 */
animation: rot 5s ease-in-out 2s 3;
}
/* 动画 :旋转360度 */
@keyframes rot {
100% { transform: rotate(360deg); }
}
</style>
<div class="rect"></div>
(三)元素形状变化(分步)
<style>
.rect {
margin: 100px;
width: 300px;
height: 300px;
border: 4px dotted purple;
/* 匀速、6秒一个周期、分四步(阶跃运动)、保留最后状态 */
animation: rot 6s steps(4, end) forwards;
/* animation: rot 6s steps(3, start) forwards; */
}
/* 动画 :从矩形变成圆形 */
@keyframes rot {
0% { border-radius: 0%; }
100% { border-radius: 50%; }
}
</style>
<div class="rect"></div>
(四)多个元素转圈圈
<style>
/* 父元素 */
.pa { margin: 100px auto; width: 300px; height: 300px; border-radius: 50%; border: 3px dashed gray; position: relative; }
/* 子元素 */
.rect {
position: absolute;
width: 60px;
height: 60px;
background: tan;
transform-origin: 150px 150px; /* 运动中心点坐标设在父元素中心点 */
/* 6秒一个周期、匀速、运行一百次 */
animation: ring 6s linear 100;
}
/* 第2、3子元素提前运行动画 */
.rect:nth-of-type(2) { animation-delay: -2s; }
.rect:nth-of-type(3) { animation-delay: -4s; }
/* 动画 :旋转一周 */
@keyframes ring {
to { transform: rotate(1turn); }
}
</style>
<div class="pa">
<div class="rect"></div>
<div class="rect"></div>
<div class="rect"></div>
</div>
(五)元素做椭圆运动
<style>
/* 本例 offset-path 支持百分比,所以是自适应窗口尺寸的 */
.ball {
width: 80px;
height: 80px;
border-radius: 50%;
background: linear-gradient(20deg, cyan, green);
/* 椭圆路径 :以容器(这里是body)宽高各45%做长短轴半径、圆心在body中心点 */
offset-path: ellipse(45% 45% at 50% 50%);
animation: move linear 20s infinite;
}
/* 动画 :在路径中偏移 */
@keyframes move {
0% { offset-distance: 0%; }
100% { offset-distance: 100%; }
}
</style>
<div class="ball"></div>
四、使用JS简单控制关键帧动画
对关键帧动画最常见的需求是动画的播放与暂停操作。这可以使用JS直接操作元素的 animation-play-state 属性:前面已经提到过,属性值 running 表示播放、paused 表示暂停。看实例:
🔹JS简单控制CSS关键帧动画
<style>
.kite {
cursor: pointer;
transform-origin: 100% 0;
animation: skew ease-out 0.85s infinite alternate;
}
/* 动画 :在路径中偏移 */
@keyframes skew {
to { transform: skew(5deg); }
}
</style>
<img class="kite" src="https://638183.freep.cn/638183/small/kite.png" alt="" title="点击播放/暂停" />
<script>
const kite = document.querySelector('.kite');
kite.onclick = () => {
// 使用 getAnimations API 获取元素的CSS动画相关信息
const { playState } = kite.getAnimations()[0];
// 如果动画运行中则暂停
if (playState === 'running') {
kite.style.setProperty('animation-play-state', 'paused');
// 反之继续播放
} else {
kite.style.setProperty('animation-play-state', 'running');
}
};
</script>
Element.getAnimations() API 不是必须的,上例用到它主要是为了判断元素运行动画的状态(running或paused),从而决定点击图片时动画应暂停还是继续。在实际应用场景中,可以根据诸多情况来决定动画处于何种状态,例如音画帖中关键帧动画与音频的播放、暂停状态的紧密关联。另外,如果由于某种原因不方便直接操作CSS动画播放状态属性,我们可以使用 CSS 变量来代替 animation-play-state 的属性值,直接操作该变量,这样就不用针对每一个元素进行操作,而是批量操作所有使用了该 CSS 变量的所有元素:
/* CSS 代码 :父元素定义 --state 变量值 */
.pa {
--state: running;
}
/* 子元素动画简写属性使用 --state 变量给 animation-play-state 属性赋值 */
.son {
animation: skew ease-out 0.85s infinite alternate var(--state);
}
// JS代码 :批量操作 .pa 下的所有使用了 var(--state) 的动画
const pa = document.querySelector('.pa');
// 如果要播放动画:
pa.style.setProperty('--state', 'running');
// 如果要暂停动画:
pa.style.setProperty('--state', 'paused');
对于非无穷大的动画播放次数,有时候可能需要监听动画的开始、结束动作,以便决定动画流程的下一步环节,这时有两个JS事件可用:animationstart(动画开始)和 animationend(动画结束):
🔹JS监听动画开始和动画结束事件
<style>
.rect { left: 20px; top: 100px; width: 200px; height: 100px; display: grid; place-items: center; background: wheat; position: absolute; }
.ani1 { animation: move linear 16s forwards; }
.ani2 { animation: move linear 8s reverse forwards; }
@keyframes move {
to { left: calc(100% - 220px); }
}
</style>
<div class="rect">点击开始动画</div>
<script>
const rect = document.querySelector('.rect');
const anis = ['ani1', 'ani2']; // 动画数组
let idx = 0; // 动画运行计数
// 动画开始事件
rect.onanimationstart = () => rect.textContent = `动画 :${anis[idx % 2]}`;
// 动画结束事件
rect.onanimationend = () => {
rect.classList.remove(anis[idx % 2]);
idx += 1;
setTimeout( () => rect.classList.add(anis[idx % 2]), 0);
};
rect.onclick = () => {
// 若动画尚未开始
if (!rect.getAnimations()[0]) {
rect.classList.add(anis[idx]); // 开始动画
// 若动画已经开始
} else {
const { playState } = rect.getAnimations()[0]; // 获取动画状态
// 暂停或继续动画
rect.style.setProperty(
'animation-play-state',
playState === 'running' ? 'paused' : 'running'
);
}
};
</script>
JS 操作 CSS 关键帧动画远不止这些,限于篇幅,本文点到而止。
附:animation-timing-function 属性值及其含义
| 属性值 |
属性值说明 |
| linear |
匀速,对应于贝赛尔曲线 cubic-bezier(0, 0, 1, 1)【第一控制点 {0,0},第二控制点{1,1}】 |
| ease |
慢-快-慢(缺省默认,与 ease-in-out相似,但在开始时加速更陡,对应于 cubic-bezier(0.25, 0.1, 0.25, 1.0) |
| ease-in |
先慢后快,对应于 cubic-bezier(0.42, 0.0, 1.0, 1.0) |
| ease-out |
先快后慢,对应于 cubic-bezier(0.0, 0.0, 0.58, 1.0) |
| ease-in-out |
慢-快-慢,开始时表现同 ease-in,结束时表现同 ease-out,对应于 cubic-bezier(0.42, 0.0, 0.58, 1.0) |
| cubic-bezier(x1, y1, x2, y2) |
三次贝塞尔曲线,速度曲线,n1~y2可能的值都是从 0 到 1 的数值 |
| step-start |
在变化过程中,都是以下一帧的显示效果来填充间隔动画 |
| step-end |
在变化过程中,都是以上一帧的显示效果来填充间隔动画 |
| steps() |
阶跃函数,用于实现逐帧动画(如精灵图切换),动画过程是断断续续的。可以传入两个参数,第一个是一个大于0的整数,他是将间隔动画等分成指定数目的小间隔动画,然后根据第二个参数来决定显示效果。第二个参数设置后其实和step-start,step-end同义,在分成的小间隔动画中判断显示效果 |