2、CSS过渡与动画基础:从静态到动态的第一步
说实话,CSS动画是我在React项目中用得最多的技术。为什么?因为它简单、高效,而且浏览器原生支持。你想想看,一个简单的淡入效果,用JS库可能要写几十行代码,但CSS几行就搞定了。
这一章,我会带你从最基础的CSS过渡开始,一步步深入到@keyframes动画,最后在React中实战一个淡入淡出组件。嗯,这些都是我实际项目中反复用到的套路。
2.1 CSS transition属性详解
transition,说白了就是「状态变化的平滑过渡」。比如一个按钮,鼠标移上去颜色从蓝变红,直接跳变太生硬,加上transition就丝滑了。
transition有四个子属性,我习惯这样记:
| 属性 | 作用 | 示例值 |
|---|---|---|
| transition-property | 指定哪些属性要过渡 | all, opacity, transform |
| transition-duration | 过渡持续多久 | 0.3s, 300ms |
| transition-timing-function | 速度曲线 | ease, linear, cubic-bezier() |
| transition-delay | 延迟多久开始 | 0s, 0.1s |
我在项目中遇到过一个问题:用transition: all 0.3s虽然方便,但性能很差。尤其是改变宽高、位置这些会触发重排的属性,掉帧严重。所以我建议,尽量只过渡opacity和transform,这两个属性不会触发重排。
最佳实践:transition只用在opacity和transform上,性能最好。
来看一个完整的例子:
.fade-box {
opacity: 0;
transform: translateY(10px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.fade-box.visible {
opacity: 1;
transform: translateY(0);
}
这里我用了两个属性同时过渡,效果就是「淡入 + 上移」。你想想看,如果只用opacity,元素只是原地出现,加上translateY就多了一层「从下方滑入」的感觉,用户体验好很多。
小技巧:transition的简写顺序是「属性 时长 速度曲线 延迟」,比如transition: opacity 0.3s ease 0.1s。
2.2 CSS animation与@keyframes
transition只能处理「开始」和「结束」两个状态。但有时候我们需要更复杂的动画,比如「先放大再缩小再恢复」,这时候就要用animation + @keyframes了。
@keyframes可以定义多个关键帧,每个关键帧里可以设置不同的样式。我习惯把它想象成一个「动画剧本」:
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translateY(20px);
}
50% {
opacity: 0.5;
transform: translateY(10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
然后通过animation属性来调用:
.element {
animation: fadeInUp 0.5s ease forwards;
}
animation的属性比transition多一些,我列个表:
| 属性 | 说明 | 常用值 |
|---|---|---|
| animation-name | 指定@keyframes名称 | fadeInUp, slideIn |
| animation-duration | 动画时长 | 0.5s, 1s |
| animation-timing-function | 速度曲线 | ease, ease-out |
| animation-delay | 延迟时间 | 0s, 0.2s |
| animation-iteration-count | 播放次数 | 1, infinite |
| animation-direction | 播放方向 | normal, reverse, alternate |
| animation-fill-mode | 结束后状态 | none, forwards, backwards |
注意:animation-fill-mode: forwards 很常用,它让动画结束后保持最后一帧的状态。如果不加,动画结束后元素会跳回初始状态。
我曾经踩过一个坑:用animation做无限循环的旋转动画,结果忘记加animation-fill-mode: forwards,每次循环结束元素都会闪一下。排查了半天才发现是这个问题。
2.3 在React中应用CSS动画
在React里用CSS动画,其实和普通HTML差不多。但有一个关键点:React控制的是类名的切换。
我常用的模式是这样的:
import React, { useState } from 'react';
import './FadeBox.css';
function FadeBox() {
const [visible, setVisible] = useState(false);
return (
<div>
<button onClick={() => setVisible(!visible)}>
切换
</button>
<div className={`fade-box ${visible ? 'visible' : ''}`}>
我是内容
</div>
</div>
);
}
CSS部分就是我们之前写的:
.fade-box {
opacity: 0;
transform: translateY(10px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.fade-box.visible {
opacity: 1;
transform: translateY(0);
}
你看,核心逻辑就是「通过React状态控制类名,CSS负责动画」。这种方式的好处是:动画逻辑和业务逻辑完全分离。
核心思想:React管状态,CSS管动画。各司其职,互不干扰。
2.4 实战:淡入淡出组件
好了,理论说完了,我们来写一个真正的可复用组件。这个组件我封装过很多次,每次项目里都会用到。
先看最终效果:
// FadeIn.jsx
import React, { useState, useEffect } from 'react';
import './FadeIn.css';
function FadeIn({ children, show, duration = 300 }) {
const [shouldRender, setShouldRender] = useState(show);
useEffect(() => {
if (show) {
setShouldRender(true);
} else {
// 等动画结束后再移除DOM
const timer = setTimeout(() => {
setShouldRender(false);
}, duration);
return () => clearTimeout(timer);
}
}, [show, duration]);
return (
<div
className={`fade-in ${show ? 'enter' : 'exit'}`}
style={{ '--duration': `${duration}ms` }}
>
{shouldRender && children}
</div>
);
}
export default FadeIn;
CSS部分:
.fade-in {
transition: opacity var(--duration) ease, transform var(--duration) ease;
}
.fade-in.enter {
opacity: 1;
transform: translateY(0);
}
.fade-in.exit {
opacity: 0;
transform: translateY(10px);
}
使用方式:
function App() {
const [show, setShow] = useState(true);
return (
<div>
<button onClick={() => setShow(!show)}>切换</button>
<FadeIn show={show} duration={500}>
<div style={{ padding: 20, background: '#f0f0f0' }}>
我是淡入淡出的内容
</div>
</FadeIn>
</div>
);
}
设计思路:为什么要有shouldRender?因为退出动画需要时间,如果直接移除DOM,动画就没了。所以先让动画播放,播放完再移除。
这个组件有几个亮点:
- 可配置时长:通过CSS变量传递duration,灵活控制
- 自动清理:退出时等动画结束再移除DOM,不会出现闪烁
- 通用性强:任何内容都可以包一层,直接获得淡入淡出效果
我在实际项目中,把这个组件扩展到了列表项、弹窗、提示框等各种场景。你想想看,一个组件写好了,到处都能用,是不是很爽?
避坑指南:如果动画不生效,先检查CSS类名是否拼写正确。我曾经因为把className写成class,排查了半小时...
嗯,这一章的内容就到这里。CSS过渡和动画是React动画的基础,掌握了这些,后面学更复杂的动画库会轻松很多。下一章我们会讲React Transition Group,那个库就是基于我们今天学的这些原理封装的。