CSS 动画与过渡效果

小飞兽 HTML/CSS 516 次阅读 2026-05-16

简介

CSS 动画与过渡是现代网页设计中不可或缺的技术,它们为用户界面注入了生命力和交互反馈。过渡效果(Transition)适用于属性值在两个状态之间的平滑变化,而动画(Animation)则支持更复杂的多阶段、多关键帧的动态效果。

在 Web 早期,页面的状态切换是瞬间完成的,用户体验单调且缺乏反馈感。随着 CSS3 引入的 transition@keyframes 动画,开发者无需依赖 JavaScript 或 Flash 就能创建流畅的动效。这不仅提升了用户体验,还显著改善了页面性能——因为 CSS 动画可以借助 GPU 加速,在主线程之外独立运行。

本文将系统讲解 CSS 过渡与动画的基础语法、工作原理、代码示例和最佳实践,帮助你掌握这两项核心技能。

基础语法

CSS 过渡(Transition)

过渡效果允许我们在属性值发生变化时,控制属性值过渡的时长和缓动函数。

核心属性

  • <code>transition-property</code>:指定要过渡的 CSS 属性名

  • <code>transition-duration</code>:过渡持续时间(默认 0)

  • <code>transition-timing-function</code>:缓动函数,控制过渡的速度变化

  • <code>transition-delay</code>:过渡延迟时间

  • <code>transition</code>:以上四个属性的简写形式

/* 完整写法 */
.my-element {
    transition-property: all;
    transition-duration: 0.3s;
    transition-timing-function: ease-in-out;
    transition-delay: 0s;
}

/* 简写 */
.my-element {
    transition: all 0.3s ease-in-out;
}

/* 多个属性分开写 */
.my-element {
    transition-property: background-color, transform;
    transition-duration: 0.3s, 0.5s;
    transition-timing-function: ease, cubic-bezier(0.4, 0, 0.2, 1);
}

常用缓动函数

  • <code>ease</code>:默认值,慢启动,快过程,慢结束

  • <code>linear</code>:匀速变化

  • <code>ease-in</code>:慢启动

  • <code>ease-out</code>:慢结束

  • <code>ease-in-out</code>:慢启动和慢结束

  • <code>cubic-bezier(n, n, n, n)</code>:自定义贝塞尔曲线

CSS 动画(Animation)

动画通过 @keyframes 定义关键帧,然后通过 animation 相关属性控制播放。

核心属性

  • <code>animation-name</code>:关键帧名称

  • <code>animation-duration</code>:动画持续时间

  • <code>animation-timing-function</code>:缓动函数

  • <code>animation-delay</code>:动画延迟

  • <code>animation-iteration-count</code>:播放次数(默认 1,可为 <code>infinite</code>)

  • <code>animation-direction</code>:播放方向(<code>normal</code>、<code>reverse</code>、<code>alternate</code>、<code>alternate-reverse</code>)

  • <code>animation-fill-mode</code>:动画前后的样式状态(<code>none</code>、<code>forwards</code>、<code>backwards</code>、<code>both</code>)

  • <code>animation-play-state</code>:播放状态(<code>running</code>、<code>paused</code>)

  • <code>animation</code>:简写属性

/* 定义关键帧 */
@keyframes slideIn {
    from {
        opacity: 0;
        transform: translateX(-100px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}

/* 应用动画 */
.my-element {
    animation: slideIn 0.5s ease-out forwards;
}

代码示例

示例一:按钮悬停过渡效果

最常见的过渡应用——按钮在鼠标悬停时改变背景色和添加阴影。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
        }
        .btn {
            padding: 14px 32px;
            font-size: 16px;
            font-weight: 600;
            color: #fff;
            background: rgba(255, 255, 255, 0.2);
            border: 2px solid rgba(255, 255, 255, 0.4);
            border-radius: 50px;
            cursor: pointer;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            outline: none;
            backdrop-filter: blur(10px);
        }
        .btn:hover {
            background: rgba(255, 255, 255, 0.35);
            transform: translateY(-3px);
            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
            border-color: rgba(255, 255, 255, 0.6);
        }
        .btn:active {
            transform: translateY(-1px);
            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
        }
        .btn-primary {
            background: #fff;
            color: #667eea;
            border-color: #fff;
        }
        .btn-primary:hover {
            background: #f0f0f0;
            color: #5a6fd6;
        }
    </style>
</head>
<body>
    <button class="btn">透明玻璃按钮</button>
    <button class="btn btn-primary">主要按钮</button>
</body>
</html>

示例二:卡片翻转动画

使用 3D 变换和动画实现卡片翻转效果,展示 CSS 动画的强大能力。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            background: #1a1a2e;
            perspective: 1000px;
        }
        .card-container {
            width: 280px;
            height: 360px;
            perspective: 1000px;
        }
        .card {
            width: 100%;
            height: 100%;
            position: relative;
            transform-style: preserve-3d;
            transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
            cursor: pointer;
        }
        .card:hover {
            transform: rotateY(180deg);
        }
        .card-face {
            position: absolute;
            width: 100%;
            height: 100%;
            backface-visibility: hidden;
            border-radius: 20px;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            padding: 30px;
        }
        .card-front {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }
        .card-front h2 {
            font-size: 24px;
            margin-bottom: 15px;
        }
        .card-front p {
            text-align: center;
            opacity: 0.9;
            line-height: 1.6;
        }
        .card-back {
            background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
            color: white;
            transform: rotateY(180deg);
        }
        .card-back h3 {
            font-size: 20px;
            margin-bottom: 20px;
        }
        .card-back ul {
            list-style: none;
            text-align: left;
        }
        .card-back li {
            padding: 8px 0;
            border-bottom: 1px solid rgba(255,255,255,0.2);
            font-size: 14px;
        }
        .hint {
            color: rgba(255,255,255,0.6);
            font-size: 14px;
            margin-top: 30px;
        }
    </style>
</head>
<body>
    <div class="card-container">
        <div class="card">
            <div class="card-face card-front">
                <h2>卡片正面</h2>
                <p>悬停查看卡片背面</p>
                <p class="hint">Hover to flip ↻</p>
            </div>
            <div class="card-face card-back">
                <h3>卡片背面</h3>
                <ul>
                    <li>✓ CSS 3D Transform</li>
                    <li>✓ Backface Visibility</li>
                    <li>✓ Transition Timing</li>
                    <li>✓ Cubic Bezier Easing</li>
                </ul>
            </div>
        </div>
    </div>
</body>
</html>

示例三:加载动画

使用纯 CSS 实现一个旋转加载动画,替代传统的 GIF 图片。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 50px;
            background: #0f0f23;
        }
        .loader {
            width: 60px;
            height: 60px;
            border: 4px solid rgba(255, 255, 255, 0.1);
            border-top-color: #00d2ff;
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }
        .loader-dots {
            display: flex;
            gap: 8px;
        }
        .loader-dots span {
            width: 12px;
            height: 12px;
            background: #00d2ff;
            border-radius: 50%;
            animation: bounce 1.4s ease-in-out infinite both;
        }
        .loader-dots span:nth-child(1) { animation-delay: -0.32s; }
        .loader-dots span:nth-child(2) { animation-delay: -0.16s; }
        .loader-dots span:nth-child(3) { animation-delay: 0s; }

        .pulse-ring {
            width: 60px;
            height: 60px;
            border-radius: 50%;
            border: 3px solid #764ba2;
            animation: pulse-ring 1.5s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
        }

        @keyframes spin {
            to { transform: rotate(360deg); }
        }
        @keyframes bounce {
            0%, 80%, 100% {
                transform: scale(0);
            }
            40% {
                transform: scale(1);
            }
        }
        @keyframes pulse-ring {
            0% {
                transform: scale(0.8);
                opacity: 1;
            }
            80%, 100% {
                transform: scale(2);
                opacity: 0;
            }
        }
    </style>
</head>
<body>
    <div class="loader"></div>
    <div class="loader-dots">
        <span></span>
        <span></span>
        <span></span>
    </div>
    <div class="pulse-ring"></div>
</body>
</html>

示例四:文字渐变动画

使用背景裁剪和动画实现文字颜色动态变化效果。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            background: #0a0a0a;
        }
        .gradient-text {
            font-size: 48px;
            font-weight: 800;
            background: linear-gradient(
                90deg,
                #ff6b6b,
                #feca57,
                #48dbfb,
                #ff9ff3,
                #ff6b6b
            );
            background-size: 400% 100%;
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
            animation: gradient-shift 4s ease infinite;
        }
        @keyframes gradient-shift {
            0% { background-position: 0% 50%; }
            50% { background-position: 100% 50%; }
            100% { background-position: 0% 50%; }
        }
    </style>
</head>
<body>
    <h1 class="gradient-text">彩虹渐变文字动画</h1>
</body>
</html>

运行效果

示例一(按钮悬停):按钮在鼠标悬停时平滑地上浮(translateY(-3px)),背景从不透明变为更亮的半透明状态,阴影范围扩大并加深。整个过渡过程持续 0.3 秒,使用 cubic-bezier 缓动函数使动画具有自然的物理感。鼠标按下时按钮略微下沉,提供按压反馈。

示例二(卡片翻转):当鼠标悬停在卡片容器上时,卡片绕 Y 轴旋转 180 度。正面消失时背面同时显示,这是 backface-visibility: hidden 的效果。旋转动画使用 0.8 秒的持续时间和自定义贝塞尔曲线,使翻转过程既有速度感又不显得突兀。

示例三(加载动画):三个加载动画同时播放——第一个是匀速旋转的圆环;第二个是三个依次弹跳的圆点,波浪般的高低变化营造节奏感;第三个是向外扩散并渐隐的脉冲环,循环往复。这些都是纯 CSS 实现,无需 JavaScript 或图片资源。

示例四(文字渐变):文字的颜色在红、黄、蓝、粉之间缓慢过渡,通过移动大型渐变背景的位置并将其裁剪到文字前景来实现。动画持续 4 秒,使用 ease 缓动使渐变在颜色过渡时具有自然的加速减速感。

常见问题

问题一:过渡不生效

现象:设置了 transition 但属性变化时没有动画效果。

原因:常见的错误包括——只设置了 transition 但没有触发状态变化(如 :hover);过渡的属性名拼写错误或不是可动画的属性;属性值是 auto 时的过渡行为不可预测。

解决方案:检查是否正确添加了状态触发器(如 :hover);确保 transition-property 包含要过渡的属性名,或使用 all;将 auto 值改为具体数值。

/* 错误:没有触发状态 */
.box {
    transition: all 0.3s;
    /* 需要 :hover 等状态才能看到效果 */
}

/* 正确:添加悬停状态 */
.box:hover {
    transform: scale(1.1);
}

问题二:动画性能差、掉帧

现象:动画运行不流畅,有明显的卡顿。

原因:动画触发了浏览器的重排(reflow)或重绘(repaint),而非使用 GPU 加速。transformopacity 是可以被 GPU 优化的属性,而 widthheightmarginpadding 等的变化会触发昂贵的布局计算。

解决方案:优先使用 transformopacity 实现动画效果;开启 GPU 加速(谨慎使用 will-change);避免动画布局属性。

/* 性能差的写法 */
@keyframes move {
    to { left: 500px; } /* 触发重排 */
}

/* 性能好的写法 */
@keyframes move {
    to { transform: translateX(500px); } /* GPU 加速 */
}

/* 谨慎使用 will-change */
.element {
    will-change: transform; /* 提前告知浏览器优化 */
}

问题三:动画结束后样式回弹

现象:动画播放完毕后元素样式突然恢复到初始状态。

原因:没有设置 animation-fill-mode,默认值为 none,动画结束后会回到关键帧定义的第一帧。

解决方案:根据需求设置 animation-fill-modeforwards 保持最后一帧,backwards 应用第一帧,both 结合两者。

.my-element {
    animation: fadeIn 0.5s ease forwards;
    /* 或 */
    animation: fadeIn 0.5s ease both;
}

问题四:多个动画属性冲突

现象:同时设置多个动画时,它们的行为不符合预期。

原因animation 简写属性会重置所有未指定的子属性为其默认值。

解决方案:将所有动画属性分开写,或确保在简写属性中包含所有必要的值。

/* 分开写,避免属性被重置 */
.my-element {
    animation-name: fadeIn, slideUp;
    animation-duration: 0.5s, 0.3s;
    animation-fill-mode: both, both;
}

问题五:动画在某些设备上不流畅

现象:在移动设备或低端设备上动画明显卡顿。

原因:移动设备的 GPU 能力有限,过多或过复杂的动画会超出设备处理能力。

解决方案:简化动画——减少同时运行的动画数量;降低动画的帧率或时长;使用 @media 查询在移动设备上禁用或简化复杂动画;优先使用 CSS 动画而非 JavaScript 动画。

问题六:animation 简写属性重置子属性

现象:使用 animation 简写后,之前设置的 animation-delay 等子属性被意外重置。

原因animation 简写属性会隐式重置所有未明确指定的值。

解决方案:理解简写属性的默认值,或使用分开的属性写法。

/* 简写会重置 animation-play-state */
.element {
    animation: myAnim 2s ease 1s;
    animation-play-state: paused; /* 可能被简写重置 */
}

/* 更好的写法:明确指定所有值 */
.element {
    animation: myAnim 2s ease 1s infinite running;
}

延伸阅读

CSS 动画与过渡是前端开发中提升用户体验的重要工具。以下是进一步学习的方向和资源:

官方文档

  • MDN Web Docs 的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_transitions">CSS transitions</a> 和 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_animations">CSS animations</a> 是最权威的参考资料

  • W3C 规范 <a href="https://www.w3.org/TR/css-transitions-1/">CSS Transitions Level 2</a> 和 <a href="https://www.w3.org/TR/css-animations-1/">CSS Animations Level 2</a> 包含完整的技术细节

动画性能优化

  • Google Web Fundamentals 的 <a href="https://developers.google.com/web/fundamentals/performance/rendering">Animations and Performance</a> 深入讲解了浏览器渲染管道和动画优化策略

  • 推荐阅读《High Performance CSS Animations》了解如何编写高效的动画代码

动效设计原则

  • 查阅 Material Design 的 <a href="https://material.io/design/motion/understanding-motion.html">Motion Overview</a> 了解 Google 的动效设计理念

  • 学习 <a href="https://easings.net/">Easing Functions</a> 网站,它提供了丰富的预设缓动曲线和可视化工具

高级动画技术

  • 使用 <code>scroll-timeline</code> 实现滚动驱动的动画(Scroll-driven Animations)

  • 使用 <code>@property</code> 注册自定义属性,结合 <code>animation-timeline</code> 创建更精细的动画控制

  • 学习 Canvas 和 Web Animations API 实现更复杂的 JavaScript 驱动动画

实用工具

  • <a href="https://cubic-bezier.com/">Cubic Bezier</a> 用于创建和可视化自定义缓动曲线

  • <a href="https://animista.net/">Animista</a> 提供预制的动画代码片段

  • <a href="https://elrumordelaluz.github.io/csshake/">CSShake</a> 展示各种抖动动画效果

掌握 CSS 动画不仅仅是学习语法,更重要的是理解何时使用动画、如何设计自然的动效,以及如何确保动画的性能表现。好的动画应该服务于用户体验,而不是炫技。