交互动效设计
网页动效的核心是:让页面元素”动起来”,从而引导视线、反馈操作、增强体验。
动效本质上是改变元素的某个 CSS 属性,并控制它变化的节奏。实现手段主要有两条路线:
- 过渡 transition:由状态变化(如
:hover、:focus、类名切换)触发,平滑地从一个值过渡到另一个值。
- 动画 animation:定义好关键帧后自动播放,可以无限循环、延迟、反向等。
配合 transform 变换、filter 滤镜、clip-path 裁剪、SVG 描边动画、滚动驱动动画等,就能做出各类炫酷效果。
这篇笔记覆盖 2D/3D 变换、过渡、动画、逐帧动画、流光边框、3D 相册与导航、SVG 描边动画、裁剪、滤镜、CSS 变量与 calc、滚动时间线等动效相关知识点,适合快速复习。
一句话记忆:transition 是”从 A 过渡到 B”(需要触发),animation 是”自己会动”(自动播放)。
一、过渡 transition
1. 基本语法
1 2 3 4 5
| .box { transition: 属性名 时长 速度曲线 延迟时间; transition: all 0.3s; }
|
| 组成部分 |
说明 |
常用值 |
| 属性名 |
要过渡的 CSS 属性 |
all / transform / opacity / color 等 |
| 时长 |
过渡持续多久 |
0.3s、500ms |
| 速度曲线 |
变化快慢节奏 |
ease(默认)、linear、cubic-bezier() |
| 延迟时间 |
多久后才开始 |
0s、1s |
2. 谁触发过渡
过渡不会自己发生,需要状态改变,最常见的是 :hover:
1 2 3 4 5 6 7
| .box { width: 200px; transition: all 0.3s; } .box:hover { width: 300px; }
|
3. 哪些属性可以过渡
- 可过渡:数值类属性(
width、height、color、opacity、transform、background-color、margin、left、filter 等)。
- 不可过渡:
display、background-image(渐变/图片切换)、position 等离散属性。
⚠️ 易忘点:想让元素”淡入淡出”不要过渡 display,改用 opacity 或 visibility。
transform 用于改变元素的形状、位置、角度,不改变元素在文档流中的占位,不影响布局。
1. 位移 translate
1 2 3
| transform: translate(200px, 200px); transform: translateX(200px); transform: translateY(200px);
|
- 位移以元素自身位置为基准。
- 百分比相对于自身宽高(常用于垂直居中:
translateY(-50%))。
2. 旋转 rotate
1
| transform: rotate(360deg);
|
配合 transform-origin 改变旋转中心:
1 2 3 4 5 6
| img { transform-origin: right bottom; } img:hover { transform: rotate(360deg); }
|
transform-origin 取值:关键字(center/left/right/top/bottom)、像素、百分比。
3. 缩放 scale
1 2
| transform: scale(1.5); transform: scale(1.5, 1);
|
- 参数是比例,
1 表示原大小,小于 1 缩小、大于 1 放大。
- 同样不影响布局占位。
4. 斜切 skew
1 2
| transform: skewX(-16deg); transform: skewY(8deg);
|
常用于做出倾斜的文字标签、卡片侧边等。
5. 多组变换与顺序
多个变换用空格分隔,写在一个 transform 里:
1
| transform: translateX(600px) rotate(360deg);
|
⚠️ 易错点:多个变换按书写顺序依次作用,顺序不同结果可能不同。例如 rotate(45deg) translateX(100px) 会沿旋转后的方向移动(斜着走),而 translateX(100px) rotate(45deg) 是先平移再原地旋转。不要把多个变换拆成多个 transform 声明——后面的会覆盖前面的。
3D 变换让元素在三维空间中翻转,需要理解”透视”。
1. 三维旋转 rotateX / rotateY / rotateZ
1 2 3
| transform: rotateX(180deg); transform: rotateY(180deg); transform: rotateZ(180deg);
|
2. 透视 perspective
不加透视时,3D 旋转看不出立体感。透视有两种写法:
1 2 3 4 5 6 7 8 9
| .box { perspective: 1000px; }
img { transform: perspective(500px) rotateY(40deg); }
|
⚠️ 记忆点:透视数值越小,观察者离得越近,立体感越强;数值越大越平缓。常见取值 500px~1200px。
3. 背面隐藏 backface-visibility
翻转图片时,默认会看到背面的镜像。加 backface-visibility: hidden 隐藏背面:
1 2 3
| .box > div { backface-visibility: hidden; }
|
1 2 3
| .box { transform-style: preserve-3d; }
|
5. 3D 位移 translateZ / translate3d
1 2
| transform: translateZ(60px); transform: translate3d(0, 0, 60px);
|
6. 两面翻转图片(经典案例)
1 2 3 4 5 6 7 8 9 10 11 12
| .box { perspective: 1000px; } .box .front, .box .back { backface-visibility: hidden; transition: all 0.7s; } .box .back { transform: rotateY(180deg); } .box:hover .front { transform: rotateY(-180deg); } .box:hover .back { transform: rotateY(0deg); }
|
思路:正面和背面绝对定位叠在一起,背面预先转 180° 藏起来,hover 时两者一起翻转。
四、动画 animation
1. 定义关键帧 @keyframes
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @keyframes move { from { transform: translate(0, 0); } to { transform: translate(300px, 600px); border-radius: 50%; } }
@keyframes move2 { 0% { transform: translate(0, 0); } 25% { transform: translate(300px, 0); } 50% { transform: translate(300px, 600px); } 75% { transform: translate(0, 600px); } 100% { transform: translate(0, 0); } }
|
2. 使用动画
1 2 3
| .box { animation: move 4s; }
|
3. 完整写法(7 个子属性)
1 2
| animation: 名称 时长 速度曲线 延迟时间 播放次数 播放方向 结束状态; animation: rotate 2s linear 0s infinite normal forwards;
|
| 子属性 |
作用 |
常用值 |
animation-name |
动画名称 |
@keyframes 定义的名字 |
animation-duration |
时长 |
2s、500ms(默认 0s,不写则无效) |
animation-timing-function |
速度曲线 |
ease(默认)、linear、steps()、cubic-bezier() |
animation-delay |
延迟 |
1s;负值表示提前开始(跳过前面片段) |
animation-iteration-count |
播放次数 |
数字、infinite(无限循环) |
animation-direction |
播放方向 |
normal、reverse、alternate(交替)、alternate-reverse |
animation-fill-mode |
结束状态 |
none、forwards(停在最后一帧)、backwards、both |
4. 速度曲线 timing-function
| 值 |
效果 |
linear |
匀速 |
ease |
先慢后快再慢(默认) |
ease-in |
慢速开始,越来越快 |
ease-out |
快速开始,越来越慢 |
ease-in-out |
慢速开始和结束,中间快 |
steps(n) |
分 n 步瞬间跳变(逐帧动画) |
cubic-bezier(x1,y1,x2,y2) |
自定义贝塞尔曲线 |
⚠️ 易忘点:alternate 表示奇数遍正向、偶数遍反向,配合 infinite 做出”来回摆动”效果,如上下浮动的气泡。
5. 暂停动画 animation-play-state
1 2 3 4
| .scroll { animation: move 5s linear infinite; } .box:hover .scroll { animation-play-state: paused; }
|
6. 逐帧动画 steps()
steps(n) 让动画在 n 个瞬间跳变,常用于雪碧图逐帧动画:
1 2 3 4 5 6 7 8
| @keyframes move { 0% { background-position: 0 0; } 100% { background-position: -13700px 0; } } .box { background: url(./img/step.png) no-repeat left / 13700px 513px; animation: move 1s steps(25) infinite; }
|
思路:把一长条连续的帧图片设为背景,动画只改变 background-position,用 steps(25) 让 25 帧逐帧跳变,形成动画。
7. 多个动画同时播放
用逗号分隔即可:
1
| animation: move 5s linear infinite, rotate 30s linear infinite alternate;
|
五、动效常用技巧与案例
1. 流光渐变边框按钮
思路:一个渐变背景的伪元素不断平移,外层 overflow: hidden 只露出边框一圈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| .btn { position: relative; overflow: hidden; padding: 3px; } .btn::before { content: ''; position: absolute; width: 200%; height: 100%; background: linear-gradient(115deg, #4fcf70, #fad648, #a767e5, #12bcfe, #44ce7b); background-size: 50% 100%; animation: move 1s linear infinite; } @keyframes move { to { transform: translateX(-50%); } }
|
另一种用 conic-gradient(圆锥渐变)旋转做出光晕边框,再用 ::after + inset 盖住中心:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| .btn2::before { content: ''; position: absolute; width: 500px; height: 500px; background-image: conic-gradient(transparent, transparent, transparent, #00ccff); animation: rotate 1s linear infinite; } .btn2::after { content: ''; position: absolute; inset: 3px; background-color: #000; border-radius: 10px; }
|
记忆点:inset 是 top/right/bottom/left 四值的简写;conic-gradient 是绕中心旋转的圆锥渐变,适合做流光。
2. 卡片折叠(:has() 选择器)
鼠标悬停某张卡片时,用 :has() 反向控制父容器,让悬停的卡片变宽:
1 2 3 4 5 6
| ul { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; transition: all .5s; } ul:has(.item:nth-child(2):hover) { grid-template-columns: 1fr 2fr 1fr 1fr; }
|
ul:has(子选择器) 表示”包含某个状态子元素的 ul”,从而让父元素响应子元素的 hover,非常实用。
3. 图片轮播(无缝滚动)
思路:放两组完全相同的内容,整体 translateX 移动一组宽度后瞬间回到起点,视觉上无缝循环;hover 暂停。
1 2 3 4 5 6 7 8 9
| .scroll { display: flex; animation: move 5s linear infinite; } @keyframes move { 0% { transform: translateX(0); } 100% { transform: translateX(-1200px); } } .box:hover .scroll { animation-play-state: paused; }
|
4. 3D 悬停相册(倒影 + 兄弟选择器)
1 2 3 4 5 6 7 8 9 10 11 12 13
| .item { -webkit-box-reflect: below 1px linear-gradient(transparent, #0002); } .item:hover { transform: scale(1.2); z-index: 1; }
.box:hover .item:not(:hover) { transform: perspective(500px) scale(0.9) rotateY(45deg); }
.box .item:hover ~ .item { transform: perspective(500px) scale(0.9) rotateY(-45deg); }
|
要点:
-webkit-box-reflect:倒影,参数为(位置 above/below + 偏移 + 渐变)。
:not(:hover):排除当前悬停元素,选中其余元素。
~:后续兄弟选择器,选中之后的所有兄弟。
- 悬停元素要加
z-index: 1 盖住其他卡片。
5. 3D 导航(preserve-3d)
每个菜单项有正面和底面两个面,hover 时绕 X 轴翻转:
1 2 3 4 5 6
| ul li { transform-style: preserve-3d; } ul li .front { transform: translateZ(20px); } ul li .bottom { transform: translateY(20px) rotateX(-90deg); } ul li:hover { transform: rotateX(90deg); }
|
六、SVG 与描边动画
1. SVG 基础
- SVG 是矢量图,放大不失真,常用来做图标。
- 通过 CSS 的
width / height 控制大小(color 对 SVG 无效)。
viewBox 定义坐标系统,path 的 d 属性描述路径。
1 2 3
| <svg viewBox="0 0 1024 1024" width="200" height="200"> <path d="M512 928A416 ..." /> </svg>
|
2. 描边相关属性
1 2 3 4 5
| svg { fill: none; stroke: blue; stroke-width: 3px; }
|
| 属性 |
作用 |
fill |
填充颜色(路径内部) |
stroke |
描边颜色(路径线条) |
stroke-width |
描边宽度 |
stroke-dasharray |
虚线模式:”实线长度 空白长度” |
stroke-dashoffset |
虚线起始偏移,增大表现为”擦除” |
3. 描边动画(画出来的效果)
核心是 stroke-dasharray 和 stroke-dashoffset:
1 2 3 4 5 6 7 8 9 10 11
| svg { fill: none; stroke: red; stroke-dasharray: 2783; stroke-dashoffset: 2783; animation: heart 5s linear infinite; } @keyframes heart { 0% { stroke-dashoffset: 2783; } 100% { stroke-dashoffset: 0; } }
|
⚠️ 记忆点:stroke-dashoffset 越大,线条被”擦除”得越多。把 dasharray 和 dashoffset 都设成路径总长度,再把 offset 从总长度动画到 0,就实现”一笔画”效果。路径总长度可用 JS getTotalLength() 获取,或直接估一个足够大的值。
4. SVG 拆分动画
SVG 里可用 <g> 把多个图形分组,对分组加 transform(SVG 元素用 transform 属性而不是 CSS transform):
1 2 3
| <g class="drone" transform="translate(42 54)"> </g>
|
1 2 3 4 5 6
| .drone { animation: move 10s linear infinite; } @keyframes move { 0% { transform: translate(0); } 50% { transform: translate(200px); } 100% { transform: translate(0); } }
|
七、裁剪 clip-path
clip-path 用一个形状”裁剪”元素,只显示形状内的部分。
1. polygon 多边形
1
| clip-path: polygon(10% 0, 100% 1%, 90% 100%, 0% 100%);
|
- 参数是若干坐标点(x% y%),按顺序连线成多边形。
- 常配合
transition 让裁剪区域平滑变化(图片切换效果)。
2. circle 圆形
1
| clip-path: circle(100px at 0% 50%);
|
3. 文字裁剪 background-clip: text
让渐变”填充”文字:
1 2 3 4 5 6 7 8 9 10
| h1 { color: #292929; position: relative; } h1::after { content: "HELLOWORLD"; color: transparent; background: linear-gradient(to right, #ff69b3, #fe0000, #ffff01, #9400d4); background-clip: text; -webkit-background-clip: text; clip-path: circle(100px at 0% 50%); animation: move 2s linear infinite; }
|
八、滤镜 filter 与背景滤镜 backdrop-filter
1. filter 常见函数
1 2 3 4 5 6 7 8
| filter: blur(5px); filter: brightness(150%); filter: contrast(150%); filter: saturate(150%); filter: grayscale(100%); filter: hue-rotate(90deg); filter: sepia(100%); filter: drop-shadow(5px 5px 5px red);
|
| 函数 |
作用 |
blur() |
高斯模糊,数值越大越糊 |
brightness() |
亮度,100% 为原值 |
contrast() |
对比度 |
saturate() |
饱和度,0 变灰 |
grayscale() |
灰度,100% 黑白 |
hue-rotate() |
色相旋转 |
sepia() |
复古褐色 |
drop-shadow() |
投影 |
2. drop-shadow 与 box-shadow 区别
box-shadow:给盒子加阴影(矩形)。
drop-shadow:按元素实际形状/透明区域轮廓加阴影,对带透明背景的 PNG、SVG 更真实。
3. 背景滤镜 backdrop-filter(毛玻璃)
backdrop-filter 作用于元素背后的内容,实现毛玻璃/磨砂效果:
1 2 3 4 5
| .card { background: rgba(255, 255, 255, 0.4); backdrop-filter: blur(18px); backdrop-filter: saturate(150%) blur(28px); }
|
记忆点:filter 模糊元素自己,backdrop-filter 模糊元素背后的内容。毛玻璃 = 半透明背景 + backdrop-filter blur。
九、CSS 变量 var 与计算函数 calc
1. 变量定义与使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| :root { --color: blue; --gap: 16px; }
.box { color: var(--color); border: 1px solid var(--color); }
.nav { --fz: 20px; }
|
- 变量名以
-- 开头,用 var(--变量名) 引用。
- 可加默认值:
var(--fz, 16px)(变量不存在时用 16px)。
2. calc 计算函数
1 2 3
| .item { width: calc(33.3333333% - var(--gap)); }
|
calc() 里 +、- 两边要加空格(*、/ 可不加)。
- 可混合单位,如
calc(100% - 20px)。
3. var + calc 的典型应用
① 精灵图定位:用内联变量 --i 计算背景位置,逐行偏移:
1 2 3
| ul li { background: url(./img/sprite.png) no-repeat 0 calc(var(--i) * -58px); }
|
1 2 3
| <li style="--i:0;"></li> <li style="--i:1;"></li> <li style="--i:2;"></li>
|
② 交错动画延迟:每个元素延迟不同,形成依次出现效果:
1 2 3
| ul li { transition: all 0.5s calc(0.1s * var(--i)); }
|
③ 扇形展开角度:每个子项转不同角度散开:
1 2 3 4 5 6
| .menu:hover span { transform: rotate(calc(45deg * var(--i))) translateX(80px); } .menu:hover span i { transform: rotate(calc(-45deg * var(--i))); }
|
记忆点:--i 通常是写在内联 style 里按序递增的自定义索引,配合 calc() 批量生成规律性数值,是动效里非常常见的套路。
十、滚动驱动动画 animation-timeline
让动画的播放进度由滚动控制,而非时间。
动画进度绑定页面滚动距离:
1 2 3 4 5 6 7 8 9
| .scrollbar { width: 0%; animation: grow 2s linear forwards; animation-timeline: scroll(); } @keyframes grow { 0% { width: 0%; } 100% { width: 100%; } }
|
常用于页面顶部的阅读进度条。
2. view() 视图时间线
动画进度绑定元素进入/离开视口的过程:
1 2 3 4 5 6 7 8
| .box img { animation: anim 1s linear forwards; animation-timeline: view(); } @keyframes anim { 0% { transform: scale(1); opacity: 0.2; } 100% { transform: scale(1.5); opacity: 1; } }
|
3. 配合 position: sticky 做卡片堆叠
1 2 3 4 5 6 7 8 9 10
| .main ul li { position: sticky; top: 0; animation: scale 1s ease-in-out forwards; animation-timeline: view(); } @keyframes scale { 0% { transform: scaleX(1); } 100% { transform: scaleX(0.8); } }
|
⚠️ 注意:animation-timeline: scroll()/view() 是较新特性,浏览器兼容性有限,使用时需考虑降级(不影响核心功能)。
十一、单位与独立变换属性
1. vw / vh 视口单位
1 2 3 4
| .box { width: 50vw; height: 50vh; }
|
vw:相对视口宽度,100vw = 整个视口宽。
vh:相对视口高度,100vh = 整个视口高。
- 常用于整屏布局、响应式字号:
font-size: calc(1em + 0.5vw)。
2. turn 角度单位
1 2
| transform: rotate(0.5turn); transform: rotate(1turn);
|
1turn = 360deg,写整圈时比 360deg 更直观。
3. 独立变换属性(新特性)
除了 transform 组合写法,还有独立的 translate / rotate / scale:
三者同时使用时,会按固定顺序组合,且不会相互覆盖(普通 transform 多个声明会覆盖)。
十二、速查表
| 需求 |
写法 |
| 平滑过渡 |
transition: all .3s; |
| 水平/垂直位移 |
transform: translateX(100px) / translateY(-50%) |
| 旋转 |
transform: rotate(45deg) |
| 缩放 |
transform: scale(1.2) |
| 斜切 |
transform: skewX(-16deg) |
| 旋转中心 |
transform-origin: right bottom; |
| 3D 翻转 |
transform: rotateY(180deg); |
| 透视 |
父元素 perspective: 1000px; |
| 隐藏背面 |
backface-visibility: hidden; |
| 开启 3D 空间 |
transform-style: preserve-3d; |
| 定义动画 |
@keyframes 名 { from{} to{} } |
| 使用动画 |
animation: 名 2s linear infinite; |
| 逐帧动画 |
animation: 名 1s steps(25) infinite; |
| 停在最后一帧 |
animation-fill-mode: forwards; |
| 暂停动画 |
animation-play-state: paused; |
| 流光边框 |
conic-gradient + ::before 旋转 + overflow: hidden |
| 父级响应子级 hover |
ul:has(.item:hover) |
| 排除当前元素 |
.item:not(:hover) |
| 后续兄弟 |
.item:hover ~ .item |
| SVG 描边 |
stroke / stroke-width / fill: none |
| 一笔画动画 |
stroke-dasharray + stroke-dashoffset 动画 |
| 裁剪 |
clip-path: polygon(...) / circle(...) |
| 文字渐变 |
background-clip: text + color: transparent |
| 图片模糊 |
filter: blur(5px) |
| 毛玻璃 |
backdrop-filter: blur(18px) |
| 倒影 |
-webkit-box-reflect: below 1px linear-gradient(transparent,#0002) |
| 全局变量 |
:root { --x: 值 } + var(--x) |
| 计算 |
calc(33.333% - var(--gap)) |
| 视口单位 |
100vw / 100vh |
| 滚动进度条 |
animation-timeline: scroll(); |
| 元素进入视口触发 |
animation-timeline: view(); |