Skip to content

CSS 过渡:为状态变化添加平滑动效

观察一扇门的开关:粗暴地推开会发出刺耳的碰撞声,而缓缓推开则优雅而自然。在用户界面中,从一个状态瞬间跳转到另一个状态就像粗暴推门——突兀且不友好。CSS 过渡(Transition)为状态变化添加了时间维度,让按钮的悬停、菜单的展开、颜色的改变都变得流畅而自然,极大提升了用户体验。

什么是 CSS 过渡?

CSS 过渡让元素的属性值从一个状态平滑地变化到另一个状态。没有过渡时,属性变化是瞬间完成的;有了过渡,变化会在指定的时间内逐渐发生。

比如一个按钮的背景色从蓝色变为绿色:

  • 没有过渡:点击瞬间,颜色从 #3498db 跳到 #2ecc71
  • 有过渡:点击后,颜色在 0.3 秒内逐渐从蓝色变为绿色

这种平滑的变化让界面更加生动、专业。

transition 的四个属性

CSS 过渡由四个属性控制,它们共同定义了过渡的行为。

transition-property:选择要过渡的属性

指定哪些 CSS 属性会有过渡效果。

css
/* 单个属性 */
.box {
  transition-property: background-color;
}

/* 多个属性,用逗号分隔 */
.box {
  transition-property: background-color, transform, opacity;
}

/* 所有可过渡的属性 */
.box {
  transition-property: all;
}

/* 不进行过渡 */
.box {
  transition-property: none;
}

注意:并非所有 CSS 属性都可以过渡。只有具有"中间值"的属性才能平滑过渡,比如颜色、尺寸、位置等。像 displayvisibility 这类只有离散值的属性无法平滑过渡。

常见可过渡属性

  • 颜色:colorbackground-colorborder-color
  • 尺寸:widthheightpaddingmargin
  • 位置:topleftrightbottom
  • 变换:transform
  • 透明度:opacity
  • 阴影:box-shadowtext-shadow

transition-duration:过渡持续时间

定义过渡从开始到结束需要多长时间。

css
/* 使用秒 */
.box {
  transition-duration: 0.3s; /* 300毫秒 */
}

/* 使用毫秒 */
.box {
  transition-duration: 500ms;
}

/* 不同属性不同时长 */
.box {
  transition-property: background-color, transform;
  transition-duration: 0.3s, 0.5s;
  /* background-color 过渡 0.3s,transform 过渡 0.5s */
}

经验法则

  • 微妙效果:100-200ms(快速响应)
  • 标准交互:200-400ms(常规按钮、链接)
  • 显著变化:400-600ms(大幅度移动、展开)
  • 复杂动画:600ms-1s(特殊效果)

过短会看不出效果,过长会让用户感觉迟缓。

transition-timing-function:缓动函数

控制过渡在时间轴上的速度曲线,就像汽车加速——可以匀速,可以先快后慢,也可以先慢后快。

css
/* 预定义关键词 */
.linear {
  transition-timing-function: linear;
  /* 匀速,始终保持相同速度 */
}

.ease {
  transition-timing-function: ease;
  /* 默认值:慢 - 快 - 慢,先加速后减速 */
}

.ease-in {
  transition-timing-function: ease-in;
  /* 慢速开始,逐渐加速 */
}

.ease-out {
  transition-timing-function: ease-out;
  /* 快速开始,逐渐减速(最常用) */
}

.ease-in-out {
  transition-timing-function: ease-in-out;
  /* 慢速开始和结束,中间加速 */
}

贝塞尔曲线:自定义缓动

使用 cubic-bezier() 函数可以创建自定义速度曲线。

css
.custom {
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  /* Material Design 的标准缓动 */
}

.bounce {
  transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
  /* 弹跳效果 */
}

贝塞尔曲线有四个参数 (x1, y1, x2, y2),定义了两个控制点。可以使用浏览器开发工具的可视化编辑器来调整。

阶跃函数:跳跃式过渡

steps() 函数将过渡分成多个步骤,而不是平滑过渡。

css
.steps-animation {
  transition-timing-function: steps(4);
  /* 分成4步完成过渡 */
}

.step-start {
  transition-timing-function: step-start;
  /* 在开始时跳跃到结束状态 */
}

.step-end {
  transition-timing-function: step-end;
  /* 在结束时跳跃到结束状态(默认) */
}

阶跃函数常用于精灵图动画、数字滚动等需要离散变化的场景。

transition-delay:延迟开始时间

设置过渡开始前的等待时间。

css
.delayed {
  transition-delay: 0.2s;
  /* 悬停后等待 0.2 秒才开始过渡 */
}

/* 不同属性不同延迟 */
.staggered {
  transition-property: opacity, transform;
  transition-duration: 0.3s, 0.3s;
  transition-delay: 0s, 0.1s;
  /* opacity 立即开始,transform 延迟 0.1s */
}

延迟可以创造错落有致的动画效果,让多个元素按顺序依次动画。

transition 简写属性

在实际开发中,更常用简写形式:

css
/* 语法:property duration timing-function delay */
.box {
  transition: background-color 0.3s ease 0s;
}

/* 简化版(使用默认值) */
.box {
  transition: background-color 0.3s;
  /* timing-function 默认 ease,delay 默认 0s */
}

/* 多个属性 */
.box {
  transition: background-color 0.3s ease, transform 0.5s ease-out 0.1s;
}

/* 所有属性使用相同设置 */
.box {
  transition: all 0.3s ease;
}

注意顺序:第一个时间值是 duration,第二个是 delay

实际应用:常见交互效果

按钮悬停效果

这是最常见的过渡应用场景。

css
.button {
  background-color: #3498db;
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: background-color 0.3s ease, transform 0.2s ease;
}

.button:hover {
  background-color: #2980b9; /* 变深 */
  transform: translateY(-2px); /* 上浮 */
}

.button:active {
  transform: translateY(0); /* 点击时回到原位 */
}

卡片提升效果

悬停时卡片"浮起",配合阴影变化。

css
.card {
  background: white;
  border-radius: 12px;
  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);
}

链接下划线动画

css
.animated-link {
  position: relative;
  text-decoration: none;
  color: #3498db;
}

.animated-link::after {
  content: "";
  position: absolute;
  bottom: -2px;
  left: 0;
  width: 0;
  height: 2px;
  background: #3498db;
  transition: width 0.3s ease;
}

.animated-link:hover::after {
  width: 100%; /* 下划线从左到右展开 */
}

图片缩放和透明度

css
.image-container {
  overflow: hidden;
  border-radius: 12px;
}

.image-container img {
  display: block;
  width: 100%;
  transition: transform 0.5s ease, opacity 0.3s ease;
}

.image-container:hover img {
  transform: scale(1.1); /* 放大 10% */
  opacity: 0.9;
}

菜单展开动画

css
.menu {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.4s ease-out;
}

.menu.open {
  max-height: 500px; /* 足够大的值 */
}

注意height: auto 无法过渡,需要使用 max-height 技巧。设置一个足够大的 max-height 值,但不要过大,否则延迟会不自然。

输入框焦点效果

css
.input-field {
  border: 2px solid #ddd;
  padding: 10px;
  border-radius: 6px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.input-field:focus {
  outline: none;
  border-color: #3498db;
  box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}

组合多个过渡:错落动画

通过不同的延迟时间,可以让多个元素依次动画,创造优雅的效果。

html
<ul class="staggered-list">
  <li>First Item</li>
  <li>Second Item</li>
  <li>Third Item</li>
  <li>Fourth Item</li>
</ul>
css
.staggered-list {
  list-style: none;
  padding: 0;
}

.staggered-list li {
  opacity: 0;
  transform: translateX(-20px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}

.staggered-list.visible li {
  opacity: 1;
  transform: translateX(0);
}

/* 使用 :nth-child 设置不同延迟 */
.staggered-list.visible li:nth-child(1) {
  transition-delay: 0.1s;
}
.staggered-list.visible li:nth-child(2) {
  transition-delay: 0.2s;
}
.staggered-list.visible li:nth-child(3) {
  transition-delay: 0.3s;
}
.staggered-list.visible li:nth-child(4) {
  transition-delay: 0.4s;
}

配合 JavaScript 添加 .visible 类,就能创造列表项依次淡入的效果。

实战案例:交互式卡片

让我们创建一个综合运用多种过渡的卡片组件:

html
<div class="interactive-card">
  <div class="card-image">
    <img src="product.jpg" alt="Product" />
    <div class="card-overlay">
      <button class="quick-view">Quick View</button>
    </div>
  </div>
  <div class="card-body">
    <h3 class="card-title">Premium Product</h3>
    <p class="card-price">$99.99</p>
    <div class="card-actions">
      <button class="btn-wishlist">♥</button>
      <button class="btn-cart">Add to Cart</button>
    </div>
  </div>
</div>
css
.interactive-card {
  width: 300px;
  background: white;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: box-shadow 0.3s ease, transform 0.3s ease;
}

.interactive-card:hover {
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  transform: translateY(-4px);
}

/* 图片区域 */
.card-image {
  position: relative;
  height: 250px;
  overflow: hidden;
}

.card-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s ease;
}

.interactive-card:hover .card-image img {
  transform: scale(1.05);
}

/* 遮罩层:默认隐藏 */
.card-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.interactive-card:hover .card-overlay {
  opacity: 1;
}

/* Quick View 按钮:从下方滑入 */
.quick-view {
  padding: 10px 20px;
  background: white;
  border: none;
  border-radius: 6px;
  font-weight: 600;
  cursor: pointer;
  transform: translateY(20px);
  transition: transform 0.3s ease 0.1s; /* 延迟 0.1s */
}

.interactive-card:hover .quick-view {
  transform: translateY(0);
}

/* 卡片内容 */
.card-body {
  padding: 20px;
}

.card-title {
  margin: 0 0 10px 0;
  font-size: 18px;
  color: #2c3e50;
  transition: color 0.2s ease;
}

.interactive-card:hover .card-title {
  color: #3498db;
}

.card-price {
  margin: 0 0 15px 0;
  font-size: 24px;
  font-weight: 700;
  color: #e74c3c;
}

/* 操作按钮区域 */
.card-actions {
  display: flex;
  gap: 10px;
}

.btn-wishlist,
.btn-cart {
  flex: 1;
  padding: 10px;
  border: 2px solid #3498db;
  background: transparent;
  color: #3498db;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
  transition: background-color 0.2s ease, color 0.2s ease, transform 0.1s ease;
}

.btn-wishlist:hover,
.btn-cart:hover {
  background-color: #3498db;
  color: white;
}

.btn-wishlist:active,
.btn-cart:active {
  transform: scale(0.95);
}

这个卡片综合运用了:

  • 卡片整体:悬停时上浮 + 阴影增强
  • 图片:缩放效果
  • 遮罩层:淡入效果
  • 按钮:延迟滑入
  • 标题:颜色变化
  • 操作按钮:背景色填充 + 点击缩放

性能优化与最佳实践

优先使用高性能属性

某些 CSS 属性的过渡会触发浏览器重排(reflow)或重绘(repaint),影响性能。

css
/* ❌ 低性能:触发重排 */
.box {
  transition: width 0.3s, height 0.3s, left 0.3s, top 0.3s;
}

/* ✅ 高性能:只触发合成 */
.box {
  transition: transform 0.3s, opacity 0.3s;
}

高性能属性(只触发合成层):

  • transform(包括 translatescalerotate
  • opacity

使用 transform: translateX() 代替 left,使用 transform: scale() 代替 width/height,性能会显著提升。

避免过渡 all

css
/* ❌ 避免:可能过渡不必要的属性 */
.box {
  transition: all 0.3s;
}

/* ✅ 推荐:明确指定属性 */
.box {
  transition: background-color 0.3s, transform 0.3s;
}

all 会过渡所有可过渡属性,可能包括你不想过渡的,导致意外效果和性能问题。

使用 will-change 提示浏览器

对于复杂的过渡,可以使用 will-change 提前通知浏览器优化。

css
.heavy-animation {
  will-change: transform, opacity;
  transition: transform 0.5s, opacity 0.5s;
}

/* 动画结束后移除 will-change */
.heavy-animation:hover {
  transform: scale(1.2);
}

注意:不要滥用 will-change,它会消耗额外内存。只在确实需要优化的元素上使用。

合理选择时长

css
/* ✅ 根据变化幅度调整时长 */
.subtle-change {
  transition: opacity 0.15s; /* 微妙变化用短时长 */
}

.significant-change {
  transition: transform 0.5s; /* 大幅度变化用长时长 */
}

常见问题与解决方案

问题 1:过渡在页面加载时触发

页面加载时,元素从默认样式变为定义样式可能触发过渡。

css
/* ❌ 问题:加载时就有过渡 */
.box {
  opacity: 0;
  transition: opacity 0.5s;
}

/* ✅ 解决方案:使用类名控制 */
.box {
  opacity: 0;
}

.box.loaded {
  opacity: 1;
  transition: opacity 0.5s;
}
javascript
// 页面加载后添加类
window.addEventListener("load", () => {
  document.querySelector(".box").classList.add("loaded");
});

问题 2:height: auto 无法过渡

auto 值没有具体数值,浏览器无法计算中间状态。

css
/* ❌ 无法工作 */
.menu {
  height: 0;
  transition: height 0.3s;
}
.menu.open {
  height: auto; /* 无法过渡 */
}

/* ✅ 解决方案1:使用 max-height */
.menu {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease-out;
}
.menu.open {
  max-height: 500px; /* 大于实际高度 */
}

/* ✅ 解决方案2:使用 JavaScript 计算高度 */
javascript
const menu = document.querySelector(".menu");
menu.style.height = menu.scrollHeight + "px";

问题 3:过渡结束后需要执行代码

使用 transitionend 事件监听过渡结束。

javascript
const box = document.querySelector(".box");

box.addEventListener("transitionend", (e) => {
  console.log(`${e.propertyName} transition completed`);
  // 执行后续操作
});

问题 4:某些浏览器不支持

老版本浏览器可能需要前缀。

css
.box {
  -webkit-transition: transform 0.3s; /* Safari */
  -moz-transition: transform 0.3s; /* Firefox */
  -o-transition: transform 0.3s; /* Opera */
  transition: transform 0.3s;
}

现代构建工具(如 Autoprefixer)会自动添加前缀。

过渡 vs 动画

很多人会混淆 CSS 过渡和 CSS 动画,它们的区别在于:

特性Transition(过渡)Animation(动画)
触发方式状态变化时自动触发可以自动播放,无需触发
循环只播放一次(往返两次)可以无限循环
控制点只有开始和结束可以定义多个关键帧
复杂度简单的 A → B 变化复杂的多步骤动画
适用场景交互反馈(悬停、点击)持续动画(加载器、装饰)

何时使用过渡

  • 按钮悬停效果
  • 表单焦点状态
  • 菜单展开/折叠
  • 模态框显示/隐藏

何时使用动画

  • 加载指示器
  • 无限循环的装饰动画
  • 复杂的多步骤动画
  • 入场动画(页面加载时播放)

总结

CSS 过渡是创造流畅用户体验的基础工具,它让静态网页变得生动:

  • transition-property:选择要过渡的属性
  • transition-duration:控制过渡时长
  • transition-timing-function:定义速度曲线
  • transition-delay:设置延迟开始

使用过渡时要记住:

  1. 克制使用:不是所有变化都需要过渡
  2. 时长适度:200-400ms 是最舒适的区间
  3. 性能优先:优先使用 transformopacity
  4. 明确属性:避免 all,明确指定要过渡的属性
  5. 自然缓动ease-out 通常是最自然的选择