CSS 过渡
CSS 过渡(Transitions)提供了一种在不同状态之间平滑过渡的方式,让样式变化不再突兀。与动画不同,过渡通常由用户交互(如悬停、聚焦)触发。
什么是 CSS 过渡?
CSS 过渡让属性值的变化在一段时间内平滑进行,而不是立即生效。例如,当鼠标悬停在按钮上时,背景色可以从一种颜色渐变到另一种颜色,而不是瞬间切换。
过渡与动画的区别
| 特性 | 过渡 (Transition) | 动画 (Animation) |
|---|---|---|
| 触发方式 | 需要触发条件(hover、focus 等) | 可自动播放 |
| 状态数量 | 两个状态(起始和结束) | 可定义多个关键帧 |
| 循环播放 | 不支持 | 支持 |
| 控制精细度 | 较简单 | 更精细 |
| 适用场景 | 交互反馈、状态切换 | 复杂动画、加载效果 |
过渡属性
CSS 过渡由四个子属性组成,也可以使用简写属性 transition。
transition-property 指定过渡属性
指定哪些 CSS 属性会应用过渡效果:
/* 单个属性 */
transition-property: width;
/* 多个属性 */
transition-property: width, height, background-color;
/* 所有可过渡属性 */
transition-property: all;
/* 不过渡任何属性 */
transition-property: none;
transition-duration 过渡时长
指定过渡效果持续的时间:
/* 具体时间 */
transition-duration: 0.5s;
transition-duration: 500ms;
/* 多个属性使用不同时长 */
transition-duration: 0.3s, 0.5s, 0.8s;
transition-timing-function 时间函数
控制过渡的速度曲线:
/* 预设关键字 */
transition-timing-function: ease; /* 慢-快-慢(默认) */
transition-timing-function: linear; /* 匀速 */
transition-timing-function: ease-in; /* 慢开始 */
transition-timing-function: ease-out; /* 慢结束 */
transition-timing-function: ease-in-out; /* 慢开始和结束 */
/* 自定义贝塞尔曲线 */
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
/* 步进函数 */
transition-timing-function: steps(4, end);
transition-timing-function: step-start;
transition-timing-function: step-end;
常用时间函数详解:
| 函数 | 效果 | 适用场景 |
|---|---|---|
ease | 开始慢,中间快,结束慢 | 通用过渡,自然效果 |
linear | 匀速 | 进度条、颜色循环 |
ease-in | 开始慢,逐渐加速 | 元素离开屏幕 |
ease-out | 开始快,逐渐减速 | 元素进入屏幕 |
ease-in-out | 慢速开始和结束 | 需要平滑过渡的效果 |
transition-delay 延迟时间
指定过渡效果开始前的等待时间:
/* 延迟 0.5 秒后开始 */
transition-delay: 0.5s;
/* 多个属性使用不同延迟 */
transition-delay: 0s, 0.2s, 0.4s;
/* 负值:从过渡中间开始 */
transition-delay: -0.5s;
transition 简写属性
/* 完整语法 */
transition: property duration timing-function delay behavior;
/* 基本示例 */
transition: width 0.3s ease;
transition: background-color 0.5s linear 0.2s;
transition: all 0.3s ease-in-out;
/* 包含 transition-behavior(用于离散属性) */
transition: opacity 0.3s, display 0.3s allow-discrete;
transition: opacity 0.3s, overlay 0.3s allow-discrete;
/* 多个过渡 */
transition:
width 0.3s ease,
height 0.3s ease 0.1s,
background-color 0.5s ease;
/* 注意:当有两个时间值时,第一个是 duration,第二个是 delay */
transition: transform 0.3s 0.2s;
/* duration: 0.3s, delay: 0.2s */
简写语法中的 transition-behavior:
transition-behavior 可以作为简写的一部分放在最后:
/* 兼容性写法:分两行声明 */
.modal {
transition: opacity 0.3s, transform 0.3s;
transition: opacity 0.3s, transform 0.3s, display 0.3s allow-discrete;
/* 不支持 transition-behavior 的浏览器会忽略第二行 */
}
可过渡的属性
并非所有 CSS 属性都支持过渡。以下是常见的可过渡属性:
变换属性(推荐)
/* transform 和 opacity 性能最佳 */
transform: translateX(100px);
transform: scale(1.2);
transform: rotate(45deg);
opacity: 0.5;
盒模型属性
width: 200px;
height: 100px;
max-width: 500px;
margin: 20px;
padding: 10px;
top: 50px;
left: 100px;
边框属性
border-width: 2px;
border-color: #333;
border-radius: 10px;
outline-width: 2px;
文本属性
color: #ff0000;
font-size: 18px;
font-weight: 700;
letter-spacing: 2px;
line-height: 1.8;
背景属性
background-color: #3498db;
background-position: 50% 50%;
其他属性
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
visibility: visible;
z-index: 100;
离散属性(需要特殊处理)
以下属性是离散属性,传统上不支持过渡,但使用 transition-behavior: allow-discrete 可以启用过渡:
| 属性 | 说明 | 过渡行为 |
|---|---|---|
display | 元素显示方式 | 从 none 过渡到可见值时在 0% 切换,反之在 100% 切换 |
overlay | 顶层状态 | 确保元素在过渡完成前保持在顶层 |
visibility | 可见性 | 在过渡进行到 50% 时切换 |
不可过渡的属性
以下属性完全不支持过渡:
position(但top、left等可以)floatfont-familybackground-image(但background-position可以)content
实用示例
1. 按钮悬停效果
.button {
padding: 12px 24px;
background-color: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
/* 过渡设置 */
transition:
background-color 0.3s ease,
transform 0.2s ease,
box-shadow 0.3s ease;
}
.button:hover {
background-color: #2980b9;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.button:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
2. 链接下划线动画
.link {
position: relative;
color: #333;
text-decoration: none;
}
.link::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 2px;
background-color: #3498db;
transition: width 0.3s ease;
}
.link:hover::after {
width: 100%;
}
3. 卡片悬停效果
.card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition:
transform 0.3s ease,
box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}
4. 表单输入聚焦
.input {
padding: 12px 16px;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 16px;
outline: none;
transition:
border-color 0.3s ease,
box-shadow 0.3s ease;
}
.input:focus {
border-color: #3498db;
box-shadow: 0 0 0 4px rgba(52, 152, 219, 0.2);
}
5. 下拉菜单
.dropdown {
position: relative;
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
min-width: 200px;
background: white;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
/* 初始状态 */
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition:
opacity 0.2s ease,
transform 0.2s ease,
visibility 0.2s;
}
.dropdown:hover .dropdown-menu {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
6. 图片缩放
.image-container {
overflow: hidden;
border-radius: 8px;
}
.image-container img {
width: 100%;
height: auto;
transition: transform 0.5s ease;
}
.image-container:hover img {
transform: scale(1.1);
}
7. 折叠面板
.accordion-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.accordion.open .accordion-content {
max-height: 500px; /* 需要足够大以容纳内容 */
}
8. 切换开关
.toggle {
width: 60px;
height: 30px;
background-color: #ccc;
border-radius: 15px;
position: relative;
cursor: pointer;
transition: background-color 0.3s ease;
}
.toggle::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 24px;
height: 24px;
background: white;
border-radius: 50%;
transition: transform 0.3s ease;
}
.toggle.active {
background-color: #3498db;
}
.toggle.active::after {
transform: translateX(30px);
}
离散属性过渡
传统上,像 display 这样的离散属性(discrete properties)无法进行过渡,因为它们只有两个状态,没有中间值。CSS 新增的 transition-behavior 属性和 @starting-style 规则改变了这一点,让我们能够为元素的显示和隐藏创建平滑的过渡动画。
transition-behavior 属性
transition-behavior 属性指定是否为离散动画属性启动过渡:
/* 语法 */
transition-behavior: normal; /* 默认:不对离散属性启动过渡 */
transition-behavior: allow-discrete; /* 允许对离散属性启动过渡 */
属性值说明:
| 值 | 说明 |
|---|---|
normal | 默认值,不对离散属性启动过渡 |
allow-discrete | 允许对离散属性(如 display、overlay)启动过渡 |
transition-behavior 可以作为 transition 简写属性的一部分:
/* 简写形式 */
.modal {
transition:
opacity 0.3s,
display 0.3s allow-discrete;
/* display 后面的 allow-discrete 启用离散过渡 */
}
离散动画的工作原理
离散属性的过渡有其特殊性:
普通离散属性: 在过渡进行到 50% 时瞬间切换值。
display 属性例外: 当从 display: none 过渡到可见值(如 block)时,浏览器会在 0% 时就切换到可见值,确保元素在整个过渡期间可见。反之,从可见值过渡到 none 时,会在 100% 时才切换到 none。
display: none → block: 在 0% 时就变为 block(全程可见)
display: block → none: 在 100% 时才变为 none(全程可见)
@starting-style 规则
@starting-style 用于定义元素首次显示时的起始样式。默认情况下,CSS 过渡不会在元素首次样式更新时触发,也不会在 display 从 none 变为其他值时触发。@starting-style 让我们能够覆盖这个默认行为。
两种语法形式:
/* 形式一:独立规则块 */
@starting-style {
.modal.show {
opacity: 0;
transform: scale(0.9);
}
}
/* 形式二:嵌套在现有规则中 */
.modal.show {
opacity: 1;
transform: scale(1);
@starting-style {
opacity: 0;
transform: scale(0.9);
}
}
独立的 @starting-style 规则应该放在"目标规则"之后,否则目标样式会覆盖起始样式。
overlay 属性
overlay 属性用于控制元素在顶层(top layer)的行为。当元素在顶层显示时(如 popover、dialog),overlay 会自动设置为顶层状态。过渡 overlay 属性可以确保元素从顶层移除的操作延迟到过渡完成后进行。
.modal {
transition:
opacity 0.3s,
overlay 0.3s allow-discrete;
}
完整示例:Popover 动画
Popover(弹出框)元素在显示时会被提升到顶层,隐藏时会从顶层移除并设置 display: none。要实现平滑的进出动画,需要处理三个状态:
/* 默认隐藏状态 */
[popover] {
opacity: 0;
transform: scale(0.9);
transition:
opacity 0.3s,
transform 0.3s,
display 0.3s allow-discrete,
overlay 0.3s allow-discrete;
}
/* 打开状态 */
[popover]:popover-open {
opacity: 1;
transform: scale(1);
}
/* 进场动画起始样式 */
@starting-style {
[popover]:popover-open {
opacity: 0;
transform: scale(0.9);
}
}
/* 背景遮罩动画 */
[popover]:popover-open::backdrop {
background-color: rgba(0, 0, 0, 0.5);
transition: background-color 0.3s;
}
[popover]::backdrop {
background-color: transparent;
}
HTML 使用:
<button popovertarget="menu">打开菜单</button>
<div popover id="menu">
<h3>菜单标题</h3>
<ul>
<li><a href="#">选项一</a></li>
<li><a href="#">选项二</a></li>
</ul>
</div>
完整示例:Dialog 动画
Modal Dialog(模态对话框)同样需要处理顶层和显示状态的过渡:
/* 默认隐藏状态 */
dialog {
opacity: 0;
transform: translateY(-20px);
border: none;
border-radius: 8px;
padding: 20px;
transition:
opacity 0.3s ease,
transform 0.3s ease,
display 0.3s allow-discrete,
overlay 0.3s allow-discrete;
}
/* 打开状态(模态方式) */
dialog:modal {
opacity: 1;
transform: translateY(0);
}
/* 进场动画 */
@starting-style {
dialog:modal {
opacity: 0;
transform: translateY(-20px);
}
}
/* 背景遮罩 */
dialog::backdrop {
background-color: rgba(0, 0, 0, 0);
transition: background-color 0.3s;
}
dialog:modal::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
DOM 元素添加/移除动画
当元素被添加到 DOM 或从 DOM 移除时,也可以使用过渡动画:
/* 元素的默认状态(添加后的状态) */
.item {
opacity: 1;
scale: 1;
transition:
opacity 0.3s,
scale 0.3s,
display 0.3s allow-discrete;
}
/* 元素添加时的起始样式 */
@starting-style {
.item {
opacity: 0;
scale: 0.8;
}
}
/* 元素移除时的样式 */
.item.removing {
opacity: 0;
scale: 0.8;
display: none;
}
// 添加元素动画
function addItem() {
const item = document.createElement('div');
item.className = 'item';
item.textContent = '新项目';
container.appendChild(item);
// 元素会从 @starting-style 过渡到默认样式
}
// 移除元素动画
function removeItem(item) {
item.classList.add('removing');
item.addEventListener('transitionend', (e) => {
if (e.propertyName === 'opacity') {
item.remove();
}
});
}
三种状态的理解
处理元素显示/隐藏过渡时,需要理解三种状态:
/* 1. 起始样式 - 元素首次显示时 */
@starting-style {
.box {
opacity: 0;
}
}
/* 2. 显示状态 - 元素显示后的样式 */
.box.visible {
opacity: 1;
}
/* 3. 默认状态 - 元素隐藏时的基础样式 */
.box {
opacity: 0;
}
状态转换流程:
- 显示:
@starting-style→ 显示状态 - 隐藏:显示状态 → 默认状态(不是回到 @starting-style)
这意味着进场和退场动画可以不同:
@starting-style {
.box.visible {
opacity: 0;
transform: translateY(-20px); /* 从上方进入 */
}
}
.box.visible {
opacity: 1;
transform: translateY(0);
}
.box {
opacity: 0;
transform: translateX(20px); /* 向右退出 */
}
JavaScript 控制
检测过渡事件
const element = document.querySelector('.box');
// 过渡开始前(延迟之前)
element.addEventListener('transitionrun', (e) => {
console.log('过渡即将开始');
});
// 过渡开始(延迟之后)
element.addEventListener('transitionstart', (e) => {
console.log('过渡开始');
});
// 过渡取消
element.addEventListener('transitioncancel', (e) => {
console.log('过渡被取消');
});
// 过渡结束
element.addEventListener('transitionend', (e) => {
console.log(`过渡结束: ${e.propertyName}`);
console.log(`持续时间: ${e.elapsedTime}秒`);
});
动态添加过渡
// 添加类触发过渡
element.classList.add('active');
// 移除类触发过渡
element.classList.remove('active');
// 获取计算样式确保过渡生效
element.offsetHeight; // 触发重排
// 同时设置多个属性
element.style.transition = 'all 0.3s ease';
element.style.transform = 'translateX(100px)';
过渡结束后执行操作
function fadeOut(element) {
element.style.transition = 'opacity 0.5s ease';
element.style.opacity = '0';
element.addEventListener('transitionend', function handler(e) {
if (e.propertyName === 'opacity') {
element.removeEventListener('transitionend', handler);
element.style.display = 'none';
}
});
}
性能优化
使用高性能属性
/* 推荐:transform 和 opacity 不触发布局重排 */
.good {
transition: transform 0.3s, opacity 0.3s;
}
.good:hover {
transform: translateX(10px);
opacity: 0.8;
}
/* 避免:width、height、margin 等会触发布局重排 */
.bad {
transition: width 0.3s, margin 0.3s;
}
.bad:hover {
width: 200px; /* 触发布局重排,性能较差 */
margin-left: 10px;
}
使用 will-change 提示
.animated-element {
will-change: transform, opacity;
transition: transform 0.3s, opacity 0.3s;
}
只在确实需要优化性能时使用,过度使用会消耗更多内存。
避免过渡过多属性
/* 不推荐:过渡所有属性 */
.heavy {
transition: all 0.3s;
}
/* 推荐:只过渡需要的属性 */
.light {
transition: transform 0.3s, opacity 0.3s;
}
无障碍考虑
尊重用户偏好
/* 用户偏好减少动画时禁用过渡 */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
transition-duration: 0.01ms !important;
transition-delay: 0ms !important;
}
}
提供替代方案
.element {
transition: transform 0.3s ease;
}
/* 对于不支持过渡的浏览器提供静态样式 */
@supports not (transition: all) {
.element:hover {
transform: scale(1.1);
}
}
常见问题
过渡不生效?
- 检查属性是否可过渡
- 确保起始值和结束值都存在
- 检查是否有 CSS 语法错误
- 确认过渡时长不为 0
/* 错误:缺少起始值 */
.box {
width: 100px;
transition: height 0.3s; /* height 没有初始值 */
}
.box:hover {
height: 200px;
}
/* 正确:设置初始值 */
.box {
width: 100px;
height: 100px; /* 初始值 */
transition: height 0.3s;
}
过渡闪烁?
可能是触发了不必要的布局重排,使用 transform 替代定位属性:
/* 可能闪烁 */
.bad {
transition: left 0.3s;
}
/* 更稳定 */
.good {
transition: transform 0.3s;
}
多个属性过渡时间不同步?
确保时间值列表长度匹配:
/* 正确:属性和时长一一对应 */
transition-property: width, height, opacity;
transition-duration: 0.3s, 0.5s, 0.2s;
小结
本章学习了:
- 过渡基础:transition-property、duration、timing-function、delay、behavior
- 可过渡属性:transform、opacity、颜色、尺寸等
- 时间函数:ease、linear、cubic-bezier、steps
- 实用示例:按钮效果、卡片悬停、下拉菜单等
- 离散属性过渡:transition-behavior 启用 display、overlay 等离散属性的过渡
- @starting-style:定义元素首次显示时的起始样式
- 顶层元素动画:Popover 和 Dialog 的完整进出动画实现
- DOM 元素动画:元素添加到/移除自 DOM 时的过渡效果
- JavaScript 控制:事件监听和动态控制
- 性能优化:使用 transform 和 opacity
- 无障碍:尊重用户偏好
练习
- 创建一个带悬停效果的按钮,包含颜色变化和轻微位移
- 实现一个悬停时图片放大的卡片组件
- 使用
@starting-style和transition-behavior实现一个平滑淡入的模态框 - 创建一个带进场/退场动画的 Popover 弹出框
- 实现 DOM 元素添加时的滑入动画和移除时的淡出动画
- 使用 JavaScript 检测过渡结束事件
- 使用
prefers-reduced-motion优化无障碍体验