关于 loading-states-and-perceived-performance
一项关于加载状态与感知性能的设计技能,面向设计师和开发者,涵盖加载提示选择、内容优先级,以及无法缩短的等待如何处理。
主要功能
- 01按等待时长选择加载提示
- 02使用匹配布局的骨架屏
- 03优先加载内容并延后无关数据
- 04预取可预测的后续步骤
- 05采用可回滚的乐观更新
- 06区分慢请求与长时任务
适用场景
- 01设计按钮操作和数据获取的反馈
- 02规划仪表板和页面的分阶段加载
- 03改善上传和导出期间的响应感
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dembrandt/ dembrandt-skills --skill loading-states-and-perceived-performance安装后在 Claude Code 中输入/loading-states-and-perceived-performance确认已加载。
详细介绍
加载状态与感知性能
如果用户了解自己在等 什么,以及进度 如何,等待就没那么难以忍受。感知性能是一种设计方法,旨在让系统感觉起来比实际更快。
选择合适的加载状态
| 等待时长 | 最佳模式 | 适用场景 |
|---|---|---|
| 较短(< 1s) | 行内微调器 / 加载器 | 按钮操作、小幅更新、快速获取数据 |
| 中等(1s – 3s) | 骨架屏 | 卡片、列表、仪表板、个人资料页 |
| 较长(> 3s) | 确定进度条 | 文件上传、复杂导出、繁重处理 |
| 全页 | 错落入场 / 动画分区 | 应用初次加载、主视觉区域、沉浸式过渡 |
简单场景:微调器和加载器
对于布局不会显著变化的小型独立操作,请使用微调器。
- 按钮微调器: 替换按钮文本,或显示在文本旁边。按钮应进入
disabled状态,以防止重复提交。 - 微型加载器: 用于行内更新(例如保存单个字段)的 16–24px 小圆圈。
- 动画提示: “弹簧式”旋转(缓入缓出)比匀速线性旋转更显精致。
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.spinner {
animation: spin 800ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
骨架屏(微光 / 闪光)
骨架屏提供一个视觉占位符,模拟最终内容的布局。这可以减少“布局偏移”(CLS),并明确告知用户内容将出现的位置。
闪光效果
一道微妙的渐变在骨架元素上移动。
.skeleton {
background: var(--color-grey-100);
background-image: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.5) 50%,
rgba(255, 255, 255, 0) 100%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
骨架屏规则
- 形状要匹配: 如果最终内容是圆形头像,就使用圆形骨架屏。如果是两行标题,就使用宽度不同的两条横线。
- 保持低调: 骨架屏应使用最浅的灰色(
--color-grey-100或grey-50),不应吸引用户注意。 - 渐显为真实内容: 数据到达后,让实际内容在骨架屏上方淡入(150–200ms),而不是突然切换。
完整动画分区
对于主要页面过渡或初次加载,请采用协调一致的动画策略。
错落入场(依次展开)
不要让整个页面同时出现,而是让各个分区依次播放动画。这样可以引导用户的视线从最重要的内容(主视觉区域)移向次要区域。
.section {
opacity: 0;
transform: translateY(10px);
animation: slide-up 400ms ease-out forwards;
}
/* Stagger by index */
.section:nth-child(1) { animation-delay: 100ms; }
.section:nth-child(2) { animation-delay: 200ms; }
.section:nth-child(3) { animation-delay: 300ms; }
@keyframes slide-up {
to { opacity: 1; transform: translateY(0); }
}
主视觉区域“绽放”
对于主视觉区域,可以使用更复杂的动画:
- 背景图片缓慢淡入。
- 标题略微过冲(弹簧效果)后滑入。
- CTA 按钮最后出现,并以利落的淡入或微妙的颜色过渡呈现。
按优先级加载,并预取后续内容
不要等到所有内容都准备好才开始显示。按照对用户的价值排序加载,让用户需要的内容先出现,其余内容随后补齐。这既能提升感知性能,也能提高代码效率:初始阶段获取和渲染的内容更少。
- 首先,加载价值最高的内容——关键数据、主要记录、首屏可见的答案。准备就绪后立即渲染。
- 然后,加载下一优先级的内容——次要面板、相关列表和首屏以下的分区随后逐步加载(骨架屏会占据其空间,避免页面偏移——参见上面的骨架屏部分)。
- 只获取当前视图需要的数据。 对标签页、抽屉和屏幕外的分区,等用户打开时再加载数据,不要一次性加载整页所需的数据。
预取可预测的下一步——下一页、鼠标悬停行的详情、向导的下一步——在后台提前加载,让它们可以即时显示。不要推测性地加载所有内容;只在下一步显而易见时才这样做。
高级:乐观 UI
最快的 UI,就是完全不等待服务端响应的 UI。
- 模式: 立即更新 UI,假定服务端调用会成功。如果失败,则回滚并显示错误。
- 适用场景: 点赞帖子、切换开关、重命名文件夹、删除消息。
- 优势: 为用户带来即时满足感,让应用感觉“快如闪电”。
为等待增添乐趣
加载体验不必平淡无奇。对于超过 2 秒的等待,可以考虑融入品牌个性和“乐趣”,让用户保持参与感。
符合品牌风格的微文案
用能体现品牌语气的措辞,替换通用的“加载中……”文本。
- 技术风:“正在编译数据……”“正在与云端同步……”
- 活泼风:“正在收集像素……”“正在为你的仪表板煮咖啡……”“马上就好!”
- 专业风:“正在准备报告……”“正在核实详情……”
品牌动画(Lottie/SVG)
对于较重要的加载时刻(应用初次启动、复杂数据处理),可以用简洁且契合品牌的动画替代标准旋转图标。
- 设计工具可以展示一支铅笔画出一条线。
- 健身应用可以展示跳动的心脏或奔跑的图标。
- 金融工具可以展示硬币堆叠或图表线向上移动。
渐进式讲述
如果等待时间一贯较长(3 秒以上),可以利用加载区域讲述一个小故事或提供有用信息:
- 技巧提示:“你知道可以使用 Ctrl+K 进行搜索吗?”
- **过程透明:**展示系统正在执行的操作:“正在检查数据库……” → “正在优化结果……” → “正在完成视图……”
视觉过渡(内容呈现)
从加载状态过渡到内容时,使用清晰利落的淡入效果(150ms),让内容呈现如同一份奖励。避免缩放即将显示的内容,因为这可能导致布局不稳定。
光标不是加载状态
光标用于提示可操作性:普通内容使用箭头,可执行操作的对象使用 pointer。光标从不表示进度。使用 cursor: wait 或 cursor: progress 会把状态提示放在用户不会关注的地方,既不说明正在加载什么,也不显示进度,而且指针一移动,提示就会消失。应在实际等待的元素或区域中显示进度:[[button-states]] 介绍了控件的相关做法,上文的骨架屏和进度模式则介绍了区域的相关做法。
加载状态无法做到什么
加载状态能让人更有耐心,但不能争取时间,也无法挽救注定会失败的请求。下面的阈值是长期确立的响应时间限制,并非新发现;后面要说的,是通常被忽略的推论。
- 约 10 秒以内: 微调器或骨架屏可以承载等待。
- 超过这个时间: 界面需要显示真实进度,并提供取消方式。不确定进度的动画不再传递信息,而是变成了装饰,掩盖着未知状况。
- 超过约 30 秒: 无论界面上在播放什么动画,人们都会觉得应用卡住了,并开始重新加载或离开。
如果请求经常超出这些阈值,问题就出在请求本身。 如果请求即将触发后端超时,更丰富的指示器只会推迟用户注定会遇到的错误,同时掩盖产品已出故障这一事实,让团队无从察觉。微调器只是把故障出现的时间往后推,导致没人修复根因,而用户最终还是会放弃任务。
如果等待确实无法缩短,问题也不在请求本身。 大型导出、模型调用、视频转码:有些工作需要多少时间就是多少时间,再好的指示器也无法缩短等待。此时的解决方案是让用户不必盯着界面等待。接受任务,告知用户任务正在运行,让他们去做别的事,并在任务完成后通知他们,同时把结果放在他们能找到的地方。让一个人盯着进度条等两分钟,是一种选择,而这很少是他们会做出的选择。需要避免的是把无法缩短的等待当作性能问题,又把可以优化的等待视为理所当然;两者需要截然相反的应对方式,所以在设计等待体验之前,先判断自己面对的是哪一种。
审查清单
- 加载状态是否适合预期等待时长(微调器还是骨架屏)?
- 骨架屏是否符合即将加载内容的实际布局?
- 骨架屏是否带有细微的闪烁动画,以提示“正在加载”?
- 加载期间是否禁用了按钮,以防止重复操作?
- 内容是否在骨架屏上方渐显(150–200ms),而不是突然出现?
- 对于整页加载,是否采用了交错入场效果来引导视线?
- 是否为所有加载动画遵循
prefers-reduced-motion设置? - 在“乐观 UI”场景中,如果操作失败,是否有明确的回滚路径?
- 内容是否按优先级加载(先加载价值最高的内容,其余内容随后逐步载入),并且只获取当前视图所需的内容,而不是一开始就全部加载?
- 如果下一步可以预测,是否进行了预取,让操作感觉即时,同时避免投机性地加载所有内容?
常见反模式
| 反模式 | 问题 | 修复方法 |
|---|---|---|
| 阻塞整个应用的全局微调器 | 用户非常沮丧,无法浏览其他区域 | 使用上下文加载指示器或骨架屏 |
| 与最终布局不匹配的骨架屏 | 数据到达时发生大幅布局偏移(CLS) | 精确匹配形状和尺寸 |
| 单个页面上出现太多微调器 | 造成视觉噪声,让人觉得整个应用都坏了 | 将多个加载状态归入一个容器骨架屏 |
| 速度快得离谱的骨架屏 | 闪烁动画过快或对比度过高 | 让闪烁缓慢(1.5s+)且非常细微 |