跳到主要内容

CSS 动画

CSS 动画让我们可以在不使用 JavaScript 的情况下创建流畅的动画效果。本章将详细介绍 CSS 动画的完整用法。

动画基础概念

什么是 CSS 动画?

CSS 动画让元素从一个样式逐渐变化到另一个样式。与 CSS 过渡(transition)相比,动画有以下优势:

  • 可以创建多步骤的动画序列
  • 可以循环播放
  • 更精细的控制(暂停、反向播放等)
  • 不需要触发条件(如 hover)

动画的两个部分

创建 CSS 动画需要两部分配合:

  1. @keyframes 规则:定义动画的关键帧和样式变化
  2. animation 属性:将动画应用到元素上
/* 第一部分:定义关键帧 */
@keyframes slideIn {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}

/* 第二部分:应用动画 */
.element {
animation: slideIn 0.5s ease-out;
}

@keyframes 关键帧

基本语法

@keyframes 规则定义动画的中间步骤:

@keyframes 动画名称 {
关键帧选择器 {
样式属性:;
}
}

from 和 to

使用 fromto 定义动画的起点和终点:

@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

/* 等价于使用百分比 */
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

多个关键帧

使用百分比定义更复杂的动画序列:

@keyframes bounce {
0% {
transform: translateY(0);
}
25% {
transform: translateY(-30px);
}
50% {
transform: translateY(0);
}
75% {
transform: translateY(-15px);
}
100% {
transform: translateY(0);
}
}

关键帧的要点

关键帧顺序无关紧要

浏览器会按百分比顺序执行,书写顺序不影响动画:

/* 这两种写法效果相同 */
@keyframes example1 {
100% { color: red; }
0% { color: blue; }
}

@keyframes example2 {
0% { color: blue; }
100% { color: red; }
}

省略起始帧

如果省略 0%from,浏览器使用元素的初始样式:

/* 元素会从其原始位置开始动画 */
@keyframes moveRight {
to {
transform: translateX(100px);
}
}

多个属性同时动画

@keyframes multiProperty {
0% {
opacity: 0;
transform: scale(0.5);
}
50% {
opacity: 1;
transform: scale(1.1);
}
100% {
opacity: 1;
transform: scale(1);
}
}

重复关键帧的处理

同一百分比可以出现多次,样式会合并:

@keyframes example {
50% {
top: 10px;
}
50% {
left: 20px;
}
/* 50% 时的样式是 top: 10px 和 left: 20px */
}

animation 属性详解

animation 是一个简写属性,包含以下子属性:

属性说明默认值
animation-name动画名称none
animation-duration动画时长0s
animation-timing-function时间函数ease
animation-delay延迟时间0s
animation-iteration-count播放次数1
animation-direction播放方向normal
animation-fill-mode填充模式none
animation-play-state播放状态running

animation-name 动画名称

指定要使用的 @keyframes 动画:

.element {
animation-name: slideIn;
}

/* 不应用动画 */
.element {
animation-name: none;
}

animation-duration 动画时长

指定动画完成一个周期的时间:

.element {
animation-duration: 2s; /* 2秒 */
animation-duration: 500ms; /* 500毫秒 */
animation-duration: 0.5s; /* 0.5秒 */
}
注意

如果时长为 0s(默认值),动画不会显示,但动画事件仍会触发。

animation-timing-function 时间函数

控制动画的速度曲线:

/* 关键字 */
animation-timing-function: ease; /* 慢-快-慢(默认) */
animation-timing-function: linear; /* 匀速 */
animation-timing-function: ease-in; /* 慢开始 */
animation-timing-function: ease-out; /* 慢结束 */
animation-timing-function: ease-in-out; /* 慢开始和结束 */

/* 自定义贝塞尔曲线 */
animation-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);

/* 步进动画 */
animation-timing-function: steps(4, end); /* 分4步完成 */
animation-timing-function: step-start; /* 等于 steps(1, start) */
animation-timing-function: step-end; /* 等于 steps(1, end) */

常见时间函数对比:

函数效果适用场景
ease开始慢,中间快,结束慢通用动画
linear匀速进度条、旋转
ease-in开始慢,逐渐加速元素离开屏幕
ease-out开始快,逐渐减速元素进入屏幕
ease-in-out慢速开始和结束需要平滑过渡的动画

关键帧中使用不同的时间函数:

@keyframes complex {
0% {
transform: translateX(0);
animation-timing-function: ease-out;
}
50% {
transform: translateX(100px);
animation-timing-function: ease-in;
}
100% {
transform: translateX(0);
}
}

animation-delay 延迟时间

指定动画开始前的等待时间:

.element {
animation-delay: 1s; /* 延迟1秒开始 */
animation-delay: -1s; /* 负值:从动画第1秒开始播放 */
}

负延迟的用途:

/* 让动画从中间开始播放 */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

.element {
animation: spin 4s linear infinite;
animation-delay: -2s; /* 从旋转到一半的位置开始 */
}

animation-iteration-count 播放次数

animation-iteration-count: 1;      /* 播放1次(默认) */
animation-iteration-count: 3; /* 播放3次 */
animation-iteration-count: infinite; /* 无限循环 */
animation-iteration-count: 2.5; /* 播放2.5次 */

animation-direction 播放方向

animation-direction: normal;            /* 正向播放(默认) */
animation-direction: reverse; /* 反向播放 */
animation-direction: alternate; /* 正向后反向,交替播放 */
animation-direction: alternate-reverse; /* 反向后正向,交替播放 */

alternate 的典型用法:

/* 元素来回移动 */
.element {
animation: moveRight 2s ease-in-out infinite alternate;
}

@keyframes moveRight {
from { transform: translateX(0); }
to { transform: translateX(200px); }
}
/* 播放顺序:0→200px→0→200px... */

animation-fill-mode 填充模式

控制动画执行前后的样式状态:

animation-fill-mode: none;      /* 默认:动画前后应用元素原始样式 */
animation-fill-mode: forwards; /* 动画结束后保持最后一帧样式 */
animation-fill-mode: backwards; /* 动画开始前应用第一帧样式 */
animation-fill-mode: both; /* 同时应用 forwards 和 backwards */

详细说明:

/* forwards:动画结束后保持在终点位置 */
.fade-in {
animation: fadeIn 1s forwards;
/* 动画结束后 opacity 保持为 1 */
}

@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}

/* backwards:有延迟时,在等待期间显示第一帧样式 */
.element {
animation: slideIn 1s 2s backwards;
/* 在2秒等待期间就显示 translateX(-100%) 的位置 */
}

@keyframes slideIn {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}

/* both:结合两种效果 */
.element {
animation: slideIn 1s 2s both;
/* 等待期间显示起点,结束后保持终点 */
}

animation-play-state 播放状态

animation-play-state: running;  /* 播放中(默认) */
animation-play-state: paused; /* 暂停 */

悬停暂停动画:

.element {
animation: spin 4s linear infinite;
}

.element:hover {
animation-play-state: paused;
}

animation 简写属性

/* 完整语法 */
animation: name duration timing-function delay iteration-count direction fill-mode play-state;

/* 常用示例 */
animation: slideIn 0.5s ease-out;
animation: fadeIn 1s 0.5s forwards;
animation: bounce 2s ease-in-out infinite alternate;

/* 多个动画 */
animation:
fadeIn 1s,
slideIn 1s 0.5s;
简写规则
  • 时间值有两个时,第一个是 duration,第二个是 delay
  • animation-name 建议放在最后,避免被其他值解析错误

实用动画示例

1. 淡入淡出

@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

.fade-in {
animation: fadeIn 0.5s ease-out forwards;
}

/* 淡出需要配合 JavaScript 移除元素 */
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}

.fade-out {
animation: fadeOut 0.5s ease-out forwards;
}

2. 滑入效果

/* 从左侧滑入 */
@keyframes slideInLeft {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}

.slide-in-left {
animation: slideInLeft 0.5s ease-out forwards;
}

/* 从右侧滑入 */
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}

.slide-in-right {
animation: slideInRight 0.5s ease-out forwards;
}

3. 弹跳效果

@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-30px);
}
60% {
transform: translateY(-15px);
}
}

.bounce {
animation: bounce 1s ease infinite;
}

4. 旋转加载

@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}

5. 脉冲效果

@keyframes pulse {
0% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0.7);
}
70% {
transform: scale(1.1);
box-shadow: 0 0 0 10px rgba(52, 152, 219, 0);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0);
}
}

.pulse {
animation: pulse 2s infinite;
}

6. 摇晃效果

@keyframes shake {
0%, 100% {
transform: translateX(0);
}
10%, 30%, 50%, 70%, 90% {
transform: translateX(-10px);
}
20%, 40%, 60%, 80% {
transform: translateX(10px);
}
}

.shake {
animation: shake 0.5s ease-in-out;
}

7. 打字机效果

@keyframes typing {
from {
width: 0;
}
to {
width: 100%;
}
}

@keyframes blink {
50% {
border-color: transparent;
}
}

.typewriter {
overflow: hidden;
white-space: nowrap;
border-right: 2px solid;
animation:
typing 3s steps(30) forwards,
blink 0.5s step-end infinite;
}

8. 渐变背景动画

@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}

.animated-gradient {
background: linear-gradient(270deg, #3498db, #e74c3c, #2ecc71);
background-size: 600% 600%;
animation: gradient 5s ease infinite;
}

动画与性能

可动画属性

并非所有 CSS 属性都支持动画。以下是常见的可动画属性:

变换属性(推荐使用):

  • transform(translate、scale、rotate、skew)
  • opacity

盒模型属性:

  • width、height
  • margin、padding
  • top、right、bottom、left

文本属性:

  • color、font-size
  • letter-spacing、word-spacing
  • line-height

背景属性:

  • background-color
  • background-position

性能优化建议

优先使用 transform 和 opacity

/* 推荐:使用 transform */
@keyframes move {
to {
transform: translateX(100px);
}
}

/* 避免:直接改变位置属性 */
@keyframes moveBad {
to {
left: 100px;
}
}

使用 will-change 提示浏览器

.animated-element {
will-change: transform, opacity;
}
慎用 will-change

只在需要时使用,过度使用会消耗更多资源。

避免动画盒模型属性

/* 不推荐:动画 width 会触发布局重排 */
@keyframes expand {
to { width: 200px; }
}

/* 推荐:使用 transform: scaleX() */
@keyframes expand {
to { transform: scaleX(2); }
}

无障碍考虑

减少动画偏好

尊重用户的系统设置,为不喜欢动画的用户提供替代方案:

/* 用户偏好减少动画时禁用或简化动画 */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

提供暂停控制

对于持续播放的动画,提供暂停方式:

/* 悬停暂停 */
.animated:hover {
animation-play-state: paused;
}

/* 使用按钮控制 */
.animated.paused {
animation-play-state: paused;
}

JavaScript 控制

控制动画

const element = document.querySelector('.animated');

// 暂停动画
element.style.animationPlayState = 'paused';

// 恢复动画
element.style.animationPlayState = 'running';

// 重新开始动画
element.style.animation = 'none';
element.offsetHeight; // 触发重排
element.style.animation = 'slideIn 0.5s ease-out';

监听动画事件

const element = document.querySelector('.animated');

// 动画开始
element.addEventListener('animationstart', () => {
console.log('动画开始');
});

// 动画结束
element.addEventListener('animationend', () => {
console.log('动画结束');
});

// 动画每次循环结束
element.addEventListener('animationiteration', () => {
console.log('一次循环完成');
});

小结

本章学习了:

  1. @keyframes:定义动画的关键帧和样式变化
  2. animation 属性:name、duration、timing-function、delay、iteration-count、direction、fill-mode、play-state
  3. 常用动画效果:淡入淡出、滑入、弹跳、旋转、脉冲等
  4. 性能优化:优先使用 transform 和 opacity
  5. 无障碍:尊重用户偏好,提供暂停控制

练习

  1. 创建一个淡入滑入组合动画
  2. 实现一个加载动画组件
  3. 使用 prefers-reduced-motion 优化无障碍
  4. 创建一个无限循环的背景渐变动画
  5. 用 JavaScript 控制动画的播放和暂停