CSS 动画
CSS 动画让我们可以在不使用 JavaScript 的情况下创建流畅的动画效果。本章将详细介绍 CSS 动画的完整用法。
动画基础概念
什么是 CSS 动画?
CSS 动画让元素从一个样式逐渐变化到另一个样式。与 CSS 过渡(transition)相比,动画有以下优势:
- 可以创建多步骤的动画序列
- 可以循环播放
- 更精细的控制(暂停、反向播放等)
- 不需要触发条件(如 hover)
动画的两个部分
创建 CSS 动画需要两部分配合:
- @keyframes 规则:定义动画的关键帧和样式变化
- animation 属性:将动画应用到元素上
/* 第一部分:定义关键帧 */
@keyframes slideIn {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
/* 第二部分:应用动画 */
.element {
animation: slideIn 0.5s ease-out;
}
@keyframes 关键帧
基本语法
@keyframes 规则定义动画的中间步骤:
@keyframes 动画名称 {
关键帧选择器 {
样式属性: 值;
}
}
from 和 to
使用 from 和 to 定义动画的起点和终点:
@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('一次循环完成');
});
小结
本章学习了:
- @keyframes:定义动画的关键帧和样式变化
- animation 属性:name、duration、timing-function、delay、iteration-count、direction、fill-mode、play-state
- 常用动画效果:淡入淡出、滑入、弹跳、旋转、脉冲等
- 性能优化:优先使用 transform 和 opacity
- 无障碍:尊重用户偏好,提供暂停控制
练习
- 创建一个淡入滑入组合动画
- 实现一个加载动画组件
- 使用
prefers-reduced-motion优化无障碍 - 创建一个无限循环的背景渐变动画
- 用 JavaScript 控制动画的播放和暂停