🌿 动效的四大用途
动效不是装饰,而是功能。一个合格的动效,必须能在下面四个用途里找到自己的位置; 找不到的,就该删掉。治愈田园风格的动效整体偏慢、偏柔——像风穿过麦田,而不是霓虹闪烁。
引导注意力
新内容入场、重要变化高亮。让用户的目光被"温柔地牵"到该看的地方,而不是被吓一跳。
提供反馈
点击有反应、操作有回应。按钮按下的回弹、表单提交的转圈,告诉用户"我收到了"。
建立空间关系
页面切换、展开折叠的方向感。从哪来、到哪去,用动效的朝向讲清楚,空间就不混乱。
传递个性
品牌的节奏感:治愈 = 慢、科技 = 快、活泼 = 弹。同样的过渡,曲线不同,气质完全不同。
没有目的的动效就是干扰。 每写一个动效,都先问自己一句:"它在帮用户理解什么?"答不上来,就别加。
⏱️ 时长与曲线速查
动效好不好看,70% 取决于时长和曲线对不对。记住这张表,
基本能覆盖 90% 的场景。曲线统一用 cubic-bezier,不要用 ease/linear 这种黑盒值。
| 场景 | 时长 | 曲线 | cubic-bezier |
|---|---|---|---|
| 微交互 按钮 hover / 按下 / 开关 |
100–200ms | standard(匀感) | cubic-bezier(0.4, 0, 0.2, 1) |
| 常规过渡 展开 / 折叠 / 显隐 |
200–400ms | standard | cubic-bezier(0.4, 0, 0.2, 1) |
| 入场动效 元素滚入视口 |
400–800ms | decelerate(由快到慢) | cubic-bezier(0, 0, 0.2, 1) |
| 页面切换 路由 / 抽屉 / 弹层 |
300–500ms | standard | cubic-bezier(0.4, 0, 0.2, 1) |
| 回弹 / 弹性 活泼风格 / 拖拽 |
300–500ms | spring(带过冲) | cubic-bezier(0.34, 1.56, 0.64, 1) |
这三种曲线,本站的 tokens.css 已经抽成了令牌,直接引用即可:
--motion-easing-standard: cubic-bezier(0.4, 0, 0.2, 1); /* 微交互 / 常规 */
--motion-easing-decelerate: cubic-bezier(0, 0, 0.2, 1); /* 入场:由快到慢 */
--motion-easing-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* 弹性回弹 */
实际写起来就一行——别再写 transition: all .3s 这种无曲线的版本了:
.card {
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
/* 或引用令牌:transition: all var(--motion-duration-fast) var(--motion-easing-standard); */
}
🎬 曲线长什么样?让小球跑给你看
三条同样的轨道,小球跑同样的距离——区别只在曲线。点"重播"看三遍,体会 standard(匀)、decelerate(冲一下再停)、spring(过头再弹回) 的差别。
移动端动效宁可短,不要长。 用户在等,超过 600ms 的入场会让人不耐烦。移动端把所有时长按桌面 ×0.7 收一收,体验更利落。
🌱 入场动效
元素滚入视口时,给它一个轻柔的"登场"。最常用、也最不容易出错的组合是
淡入 + 上移:opacity: 0 → 1,translateY: 16px → 0。
本站的 base.css 已经内置了这套(见 [data-reveal]),你只要加属性 + 触发脚本。
规则一:别一次全动,要错峰
一组元素同时入场会很"愣"。让每个元素比前一个晚 50–100ms,形成错峰(stagger), 视觉上就有了呼吸感。下面 5 个色块就是错峰入场——点"重播"再看一次。
规则二:用 IntersectionObserver 触发
不要用 scroll 事件(性能差、抖)。用 IntersectionObserver 监听元素进入视口,
加上 .is-visible 类即可。这也是本站实际用的脚本:
/* 元素带 data-reveal 属性,进入视口加 is-visible 类 */
const io = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
io.unobserve(entry.target); // 只动一次,避免来回滚动重复触发
}
});
}, { threshold: 0.15 }); // 露出 15% 才触发
document.querySelectorAll('[data-reveal]').forEach(el => io.observe(el));
对应的 CSS(base.css 已提供,这里展示原理):
[data-reveal] {
opacity: 0;
transform: translateY(20px);
transition:
opacity 800ms cubic-bezier(0, 0, 0.2, 1),
transform 800ms cubic-bezier(0, 0, 0.2, 1);
}
[data-reveal].is-visible {
opacity: 1;
transform: translateY(0);
}
🎬 实战 demo:滚到这里,它就淡入上来
下面这个卡片就是用上面的方法做的。点"重新播放"会移除可见态,再滚入(或再点一次)即重新触发——这正是真实页面里用户滚动时会看到的效果。
👆 Hover 与微交互
微交互是"手指/鼠标碰到时"的即时回应,时长一律 100–200ms。把鼠标移到下面这些元素上、 按住试试——治愈风格的反馈是克制的:一点点上浮、一点点加深,绝不夸张。
对应的 CSS(.btn:active 的缩放由 components.css 统一提供):
/* hover:轻微上浮 + 阴影增强 */
.btn--lift:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
/* active:缩放 0.97,模拟手指按压(已内置) */
.btn:active { transform: scale(0.97); }
卡片 hover:上浮 + 阴影
可点击的卡片,hover 时整体上浮 4px、阴影加深,暗示"我可以点"。这是本站 .card--clickable 的标准行为:
向日葵卡
移上来,我会轻轻抬起。
链接 hover:下划线渐显
正文里的链接,hover 时把淡色下划线变成实色,既不打扰阅读,又给得到反馈:
田园里有一道潺潺的小溪,把鼠标放上去看看。
💧 加载与过渡
等待不可避免,但可以让等待"有结构"。四条原则,各配了演示。
① 骨架屏优于菊花图
空白的转圈让人焦虑;骨架屏用灰块勾勒出"内容长什么样",给用户结构的预期,等待感大幅降低。下面这个会一直 shimmer 闪烁:
② 数字递增:大数字从 0 滚到目标
关键数据(销量、用户数)从 0 平滑滚到目标值,比直接显示终值更有冲击力。用 requestAnimationFrame 配 decelerate 曲线即可:
③ 进度条:平滑过渡
进度条的变化用 transition 平滑过渡,不要跳变。点击按钮,看它如何"流"到 72%:
④ 页面切换:淡入淡出 或 滑动
整页/抽屉切换,用淡入淡出(300–500ms)或带方向感的滑动。方向要和操作一致—— "前进"向左滑、"返回"向右滑,空间关系就立住了。
♿ 无障碍与降级
动效是增强,不是必需。对前庭功能敏感、易晕动、或开了"减少动态"的用户, 动效会变成折磨。下面三条是无障碍底线,必须满足。
① 必须尊重 prefers-reduced-motion
用户在系统里开了"减少动态",你的页面就该几乎不动。一行媒体查询兜底所有元素:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation: none !important;
transition: none !important;
scroll-behavior: auto !important;
}
}
这是无障碍底线,不是可选项。 关掉所有动效后,页面必须仍然完整可用——内容能读、按钮能点、状态能看清。先做"无动效版",再叠加动效。
🎬 亲手试试:模拟"减少动态"
下面这个开关,会给本页套上和 prefers-reduced-motion 等效的降级样式。打开它,再去上面点那些 demo——你会发现骨架屏不闪了、小球不滚了、数字也不计数了,但页面照样能用。这就是降级该有的样子。
② 闪烁频率低于 3Hz
任何闪烁/脉冲动画,频率必须 < 3 次/秒(低于 3Hz),以防光敏性癫痫诱发。能不闪就不闪,要闪就极慢。
③ 动效只是锦上添花
不要用动效传递关键信息(比如"红色闪烁 = 报错")。状态变化要有文字/图标/颜色等非动效冗余,关掉动效也能读懂。
🚫 常见错误
下面这些都是反复出现的坑,逐条对照自查:
- 动效太多。每个元素都在动 = 没有重点。一屏之内,同时进行的动效不超过 2–3 处。
- 时长过长。入场超过 600ms 就拖沓,移动端尤其明显。能用 400ms 就别用 800ms。
- 曲线太弹。严肃场景(表单、支付、医疗)用 spring 弹性曲线会显得轻浮。spring 只留给活泼风格和拖拽。
- 不降级。没处理
prefers-reduced-motion,对敏感用户是生理伤害。一行媒体查询的事,别省。 - hover 只在桌面有效。移动端没有 hover,光做 hover 效果 = 移动端用户毫无反馈。必须有
:active或 tap 反馈替代。 - 用 ease/linear 当万能曲线。这俩是黑盒值,不同浏览器表现不一。统一用
cubic-bezier写死,行为才可控。 - 来回滚动反复触发。入场动效触发后要用
io.unobserve()解绑,否则滚上去再下来会反复闪,既丑又耗电。
一句话总结:动效要慢得有耐心(治愈)、快得有节制(反馈)、降级得有底线(无障碍)。 做到这三点,动效就是加分项;做不到,不如不做。