跳到主要内容

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(但 topleft 等可以)
  • float
  • font-family
  • background-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 过渡不会在元素首次样式更新时触发,也不会在 displaynone 变为其他值时触发。@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;
}
慎用 will-change

只在确实需要优化性能时使用,过度使用会消耗更多内存。

避免过渡过多属性

/* 不推荐:过渡所有属性 */
.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);
}
}

常见问题

过渡不生效?

  1. 检查属性是否可过渡
  2. 确保起始值和结束值都存在
  3. 检查是否有 CSS 语法错误
  4. 确认过渡时长不为 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;

小结

本章学习了:

  1. 过渡基础:transition-property、duration、timing-function、delay、behavior
  2. 可过渡属性:transform、opacity、颜色、尺寸等
  3. 时间函数:ease、linear、cubic-bezier、steps
  4. 实用示例:按钮效果、卡片悬停、下拉菜单等
  5. 离散属性过渡:transition-behavior 启用 display、overlay 等离散属性的过渡
  6. @starting-style:定义元素首次显示时的起始样式
  7. 顶层元素动画:Popover 和 Dialog 的完整进出动画实现
  8. DOM 元素动画:元素添加到/移除自 DOM 时的过渡效果
  9. JavaScript 控制:事件监听和动态控制
  10. 性能优化:使用 transform 和 opacity
  11. 无障碍:尊重用户偏好

练习

  1. 创建一个带悬停效果的按钮,包含颜色变化和轻微位移
  2. 实现一个悬停时图片放大的卡片组件
  3. 使用 @starting-styletransition-behavior 实现一个平滑淡入的模态框
  4. 创建一个带进场/退场动画的 Popover 弹出框
  5. 实现 DOM 元素添加时的滑入动画和移除时的淡出动画
  6. 使用 JavaScript 检测过渡结束事件
  7. 使用 prefers-reduced-motion 优化无障碍体验