重学 Web 动画(一):CSS 动画基础
动画库相关
这里列举了部分流行的 css 动画库,我感觉都还不错各有特色
easings.net: 动画缓动函数,可以直接预览一些常用的动画缓动函数 cubic-bezier 函数
tw-animate-css: 一个动画库,内置了大量的动画效果,可以方便的添加到项目中
tailwindcss-radix: 一个 Tailwind CSS 的插件,将 radix 的组件状态转换为 Tailwind CSS 的类名
CSS 动画相关属性
opacity 属性
透明度,取值 0~1,0 是完全透明,1 是完全不透明。这个属性本身没什么复杂的,但它是做动画时最常用的属性之一。
最典型的场景就是淡入淡出效果:1
2
3
4
5
6
7
8.fade-in {
opacity: 0;
transition: opacity 0.3s ease;
}
.fade-in.visible {
opacity: 1;
}
值得一提的是,opacity 是可以被 GPU 加速的属性,所以用它来做动画性能很好,不会卡顿。
transform 属性
transform 是做动画的核心属性,它可以让元素移动、旋转、缩放、倾斜,而且不会触发页面重排,性能非常好。
常用的几个函数:
translate - 位移1
2
3
4
5
6
7
8/* 水平移动 */
transform: translateX(100px);
/* 垂直移动 */
transform: translateY(50px);
/* 同时移动 */
transform: translate(100px, 50px);
/* 3D 移动,可以触发 GPU 加速 */
transform: translate3d(100px, 50px, 0);
实际开发中,如果要移动元素位置,用 translate 比改 left/top 性能好太多了。因为 left/top 会触发重排,而 translate 只会触发合成。
rotate - 旋转1
2
3
4transform: rotate(45deg); /* 顺时针旋转 45 度 */
transform: rotate(-45deg); /* 逆时针旋转 45 度 */
transform: rotateX(45deg); /* 沿 X 轴旋转,有 3D 效果 */
transform: rotateY(45deg); /* 沿 Y 轴旋转 */
scale - 缩放1
2
3transform: scale(1.5); /* 放大 1.5 倍 */
transform: scale(0.5); /* 缩小到 0.5 倍 */
transform: scaleX(2); /* 只在水平方向拉伸 */
hover 放大效果用这个很方便:1
2
3.card:hover {
transform: scale(1.05);
}
组合使用
多个变换可以写在一起,但要注意顺序会影响结果:1
2
3/* 先移动再旋转 vs 先旋转再移动,效果是不一样的 */
transform: translateX(100px) rotate(45deg);
transform: rotate(45deg) translateX(100px);
transform-origin
默认情况下,变换是以元素中心为原点的。如果想改变原点位置,可以用 transform-origin:1
2
3transform-origin: top left; /* 左上角 */
transform-origin: center; /* 中心,默认值 */
transform-origin: 50% 100%; /* 底部中心 */
transition 属性
transition 是最简单的动画方式,当属性值变化时,它会自动产生过渡效果。
完整语法:1
transition: property duration timing-function delay;
举个例子:1
2
3
4
5
6
7
8.button {
background: #333;
transition: background 0.3s ease;
}
.button:hover {
background: #666;
}
四个子属性
transition-property: 要过渡的属性,可以写all表示所有属性transition-duration: 过渡时长,比如0.3s、300mstransition-timing-function: 缓动函数,这个是灵魂transition-delay: 延迟时间
timing-function 缓动函数
这是 transition 里最重要的部分。不同的缓动函数会让动画感觉完全不一样。
内置的几个:
linear: 匀速,比较生硬ease: 默认值,先快后慢ease-in: 慢进ease-out: 慢出,感觉比较自然ease-in-out: 慢进慢出
但很多时候内置的不够用,这时候就要用 cubic-bezier() 自定义曲线。前面提到的 easings.net 就是专门预览和复制这些曲线的,非常好用。1
2/* 弹性效果 */
transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
animation 属性
如果 transition 满足不了需求,比如要做循环动画、多阶段动画,就得用 animation 了。
基本语法:1
animation: name duration timing-function delay iteration-count direction fill-mode;
看起来参数很多,但常用的就那几个:1
2
3
4
5
6
7
8
9
10
11
12.loading {
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
常用子属性
animation-name: 对应 @keyframes 的名字animation-duration: 动画时长animation-timing-function: 缓动函数,和 transition 一样animation-delay: 延迟animation-iteration-count: 播放次数,infinite表示无限循环animation-direction: 播放方向normal: 正常播放reverse: 反向播放alternate: 来回播放,做呼吸灯效果很好用
animation-fill-mode: 动画结束后的状态forwards: 保持在最后一帧,这个最常用backwards: 保持在第一帧both: 两者都应用
animation-play-state:running或paused,可以用来暂停动画
tip 个人习惯
实际开发时,我一般只对时长相关不用简写(如 animation-duration),其他常见的动画属性(如 name、timing-function、iteration-count 等)可以用简写,只有遇到需要部分单独覆盖属性,或对时长有特殊需求时,才会拆开放。
比如下面这样,推荐时长写明确的单独属性,其他用简写组合没问题:1
2
3
4.loading {
animation: spin linear infinite;
animation-duration: 1s;
}
- 原因是简写最容易混淆的其实是时长相关参数(将 duration、delay 写反很常见),单独写能避免出错。
- 其他属性合并写可读性、维护都较好,但如果场景复杂、需要频繁单独覆盖某一字段,也可以全部拆开写,视团队习惯选择即可。
fill-mode 踩坑
刚学的时候经常遇到的问题:动画播完元素就跳回原位了。这时候就需要设置 animation-fill-mode: forwards,让元素保持在动画结束时的状态。
@keyframes 规则
@keyframes 用来定义动画的关键帧,有两种写法:
from…to 写法
适合简单的两帧动画:1
2
3
4
5
6
7
8@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
百分比写法
适合多阶段动画:1
2
3
4
5
6
7
8
9
10
11@keyframes bounce {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-20px);
}
100% {
transform: translateY(0);
}
}
也可以把相同的帧合并写:1
2
3
4
5
6
7
8@keyframes flash {
0%, 50%, 100% {
opacity: 1;
}
25%, 75% {
opacity: 0;
}
}
clip-path 属性
clip-path 可以裁剪元素的显示区域,做一些不规则形状的效果。配合 transition 可以做出很有意思的动画。
circle() - 圆形1
2
3
4/* 以中心为圆心,半径 50% 的圆 */
clip-path: circle(50%);
/* 指定圆心位置 */
clip-path: circle(50% at 0% 50%);
一个常见的应用是 hover 时从某个点展开:1
2
3
4
5
6
7
8.card {
clip-path: circle(0% at 50% 50%);
transition: clip-path 0.5s ease;
}
.card:hover {
clip-path: circle(100% at 50% 50%);
}
polygon() - 多边形
可以画任意多边形,参数是各个顶点的坐标:1
2
3
4/* 三角形 */
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
/* 菱形 */
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
inset() - 矩形内缩
从四边向内裁剪:1
2
3
4
5
6/* 四边各裁掉 10px */
clip-path: inset(10px);
/* 分别设置上右下左 */
clip-path: inset(10px 20px 30px 40px);
/* 还可以加圆角 */
clip-path: inset(10px round 10px);
性能优化小技巧
做动画时最怕的就是卡顿,这里总结一下怎么让动画更流畅。
只用 transform 和 opacity
这两个属性是性能最好的,因为它们只会触发合成(Composite),不会触发重排(Layout)和重绘(Paint)。
如果你要移动元素,用 transform: translate() 而不是改 left/top。要改透明度就用 opacity 而不是 visibility。
will-change 提前告知
如果知道某个元素马上要做动画,可以用 will-change 提前告诉浏览器:1
2
3.will-animate {
will-change: transform;
}
这样浏览器会提前做好优化准备。但不要滥用,用完记得移除,不然会占用额外内存。
transform: translate3d() 或 translateZ(0)
这是个小技巧,加上 Z 轴的变换可以强制开启 GPU 加速:1
2
3
4
5.gpu-accelerated {
transform: translateZ(0);
/* 或者 */
transform: translate3d(0, 0, 0);
}
避免动画这些属性
以下属性会触发重排,尽量不要用它们做动画:
width、heighttop、left、right、bottommargin、paddingfont-size
如果非要改变大小,可以用 transform: scale() 代替。
做的比较好的可参考的网站
- Vercel 官网
- Vercel 设计系统
- Linear 官网
- Aave 官网
- Aave 文档