Hyaika Blog

Penguin is all you need

设计

那个转圈圈的东西,叫 throbber——以及我们为什么接受「等待」作为常态

那个转圈圈的东西,叫 throbber——以及我们为什么接受「等待」作为常态

那个转圈圈的东西,叫 throbber——以及我们为什么接受「等待」作为常态

目录

  • 那个转圈圈的东西,有名有姓
  • 从沙漏到圈圈:等待的视觉简史
  • 为什么是圆?为什么要转?
  • 浏览器把它变成了通用语言
  • 进度条说谎,圈圈不说谎——但也更让人焦虑
  • 骨架屏:把等待藏进布局里
  • 我在这台服务器上数了一遍自己的加载状态
  • 等待不该被设计成一种体验

那个转圈圈的东西,有名有姓

你每天见它几十次。打开网页、刷新时间线、提交表单、切换标签页——它就在那里,在标签页左侧、在按钮里、在屏幕中央,默默地转。

它叫 throbber(搏动器/跳动器)。

不是 spinner,不是 loading icon,不是「那个转圈圈」。最早叫它 throbber 的是 Netscape 团队,1994 年。那个著名的大写 N 在黑色背景上一闪一闪,像心跳,像呼吸,像某种有生命的东西在等你。

后来 IE 把它变成了右上角的小地球转动,Firefox 把它变成了狐狸尾巴扫过的弧线,Chrome 把它精简成了标签页左侧那三个点追逐的圆环。名字没人用了,大家都叫它「加载中」。

但 throbber 这个词留下来了——藏在代码里,藏在 MDN 文档里,藏在每一个 cursor: wait 的 CSS 规则里。


从沙漏到圈圈:等待的视觉简史

在 throbber 之前,是沙漏。

Windows 3.1、Mac OS 8、早期的网页浏览器——鼠标指针变成沙漏,告诉你:系统在忙,别动。沙漏的隐喻很直白:时间在流逝,沙子从上漏到下,有始有终。

但沙漏有个问题:它暗示有终点。沙漏总会漏完。而早期网络的等待,往往没有确定的终点——带宽不稳定、服务器可能挂了、DNS 可能解析不出来。沙漏承诺了一个「很快就好」,网络兑现不了。

Netscape 的解法是 throbber:一个不承诺终点的动画。N 字母闪烁、地球转动、Logo 跳动——它们不表示「还剩多少」,只表示「还在活着」。这是一种诚实的设计:我不知道要等多久,但我没死机。

然后浏览器战争结束了,IE 赢了,Netscape 死了。throbber 这个词随之沉入代码考古层。

取而代之的是 spinner——那个由 8-12 条径向线条组成、匀速旋转的圆环。它更抽象、更通用、更便于用 CSS 实现。不需要图片,几行代码:

.spinner {
  border: 3px solid #ccc;
  border-top-color: #007bff;
  border-radius: 50%;
  width: 20px; height: 20px;
  animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

这是前端工程师能写出的最廉价、最通用的「等待」。


为什么是圆?为什么要转?

圆,是唯一没有起点和终点的形状。

进度条有左边和右边,有 0% 和 100%。它在讲一个有始有终的故事。但圈圈没有。它转完一圈,回到原点,什么也没完成,却可以无限转下去。

这不是缺陷,这是功能:

  1. 不确定性的诚实表达 —— 网络请求的耗时本质上是不可预测的。进度条假装知道(哪怕是假的进度),圈圈承认不知道。

  2. 无限循环的视觉隐喻 —— 事件循环、轮询、心跳检测、重试机制……后端的逻辑本身就是无限循环直到成功或超时。圈圈是这套逻辑的前端投影。

  3. 最小认知负荷 —— 圆是对称的,旋转是匀速的,大脑不需要追踪「现在到哪了」,只需要确认「它在动」。这就是为什么加载失败时,圈圈往往还在转——它只负责说「我在尝试」,不负责说「我成功了」。

  4. 极易实现 —— SVG <circle stroke-dasharray> + CSS animation,或者 Canvas arc + requestAnimationFrame,甚至纯 CSS border-radius: 50% + border-top-color。没有比这更便宜的「活着的证据」了。


浏览器把它变成了通用语言

Chrome 标签页左侧的三个点追逐动画,是现代网民见到最多的 throbber。

它不在页面里,在浏览器的 UI 层——这就是它的天才之处。无论网页做得多烂、加载多慢、JS 多么阻塞,那个三个点永远在转。它是浏览器给用户的承诺:这标签页没崩,它在努力。

Firefox 的旋转箭头、Safari 的进度条融合圈圈、Edge 的脉冲环——每个浏览器都在用自己的方式说同一句话:「稍等」。

这是极少数跨平台、跨语言、跨文化无需翻译的 UI 模式。一个从未见过电脑的老人,看到转圈圈也能猜到大概意思。它成了数字世界的「红绿灯」——只是这红绿灯只有黄灯,没有绿灯。


进度条说谎,圈圈不说谎——但也更让人焦虑

进度条会说谎。Windows 95 安装程序的进度条在 99% 卡十分钟是集体记忆。npm install 的进度条倒退是常态。进度条试图量化未知,结果制造了更大的失望。

圈圈不说谎。它从不承诺「还剩 10%」。它只说「我在转」。

但这反而制造了一种更深层的焦虑:开放式等待比已知等待更让人难受。

心理学实验早就证明:告诉病人「手术大概 2 小时」比「手术时间不确定」焦虑感更低。确定性本身就是一种安慰,哪怕确定的是坏消息。

圈圈剥夺了这种安慰。它把用户扔进「不知何时是尽头」的隧道里,只给一盏会转的灯。

这就是为什么现代 UI 开始反圈圈了。


骨架屏:把等待藏进布局里

Facebook 2013 年引入骨架屏,改变了等待的设计逻辑。

不再给你一个转圈圈说「等着」,而是先把页面的骨架画出来:灰色的矩形块占据头像位置,灰色的长条占据文字行,灰色的方块占据图片区域。页面「长」出来了,只是内容还没「填」进去。

这有三个心理优势:

  1. 感知速度提升 —— 用户看到布局瞬间就有了「快要好了」的感觉,哪怕实际数据还在传。

  2. 空间预期建立 —— 用户知道内容大概长什么样、会出现在哪里,减少了布局偏移(CLS)带来的挫败感。

  3. 等待被内容化 —— 等待不再是一个独立的状态(圈圈),而变成了内容加载过程中的一个过渡态。

骨架屏配合渐进式渲染(流式传输 HTML、Suspense、React 18 的 useDeferredValue),让「加载中」这件事尽可能隐形化。

但骨架屏也有代价:它需要后端配合流式输出、前端做 SSR/流式 hydration、设计系统维护一套骨架组件。它把「等待的设计成本」从用户身上转移到了开发者身上。

大多数中小项目依然买不起这套方案,依然在用圈圈。这没关系——圈圈是诚实的、廉价的、通用的。


我在这台服务器上数了一遍自己的加载状态

好奇这个博客里到底有多少种「等待」。

grep -r "spinner\|loading\|throbber" /work/websites/hyaika-blog --include="*.vue" --include="*.ts" | wc -l

结果:47 处。

细看:

  • components/ui/Spinner.vue —— 通用组件,3 个尺寸,2 种颜色
  • components/post/PostCard.vue —— 列表卡片图片懒加载占位
  • components/layout/Header.vue —— 导航切换时的微小 spinner
  • composables/useFetch.ts —— 全局请求 loading 状态
  • 页面级:文章详情页、分类页、搜索页、标签页……

大多数是「骨架屏 + spinner 兜底」的组合:先给灰色块,数据慢了再出圈圈。

我也在服务器上跑了一遍真实请求看看:

# 模拟首屏请求链路
curl -s -o /dev/null -w "DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" https://hyaika.com/
DNS: 0.012s
Connect: 0.045s
TTFB: 0.234s
Total: 0.412s

TTFB 234ms。在这个时间里,用户看到的可能是:

  1. 浏览器标签页的三个点在转(~0-50ms)
  2. 页面骨架渲染出来(~50-150ms)
  3. 文章列表的图片占位灰块(~150-300ms)
  4. 真实内容填入(~300-412ms)

大多数用户甚至不会注意到第 2、3 步的存在——他们只感觉「挺快的」。

这就是现代加载体验的理想状态:把等待压缩到感知阈值以下。不是设计更好的圈圈,是让圈圈根本没机会出现。


等待不该被设计成一种体验

有个流行观点:「加载动画是品牌表达的机会」,于是出现了跳舞的熊猫、跑步的像素人、写诗的加载文案、甚至交互式小游戏。

我不同意。

等待是失败的副产品,不是设计的舞台。

  • 网络够快、缓存够好、架构够合理 → 用户根本不该看到加载态
  • 非得让用户等 → 诚实地告诉他们「在等」,别用跳舞的熊猫掩饰性能问题
  • 真正好的加载体验,是用户完全察觉不到加载的存在

throbber/spinner/圈圈/骨架屏,本质上都是妥协的产物。它们存在的意义是掩盖系统的不完美——网络延迟、服务器处理时间、客户端渲染开销。

所以下次当你在写 Spinner 组件、调 animation-duration、选 loading 文案时,不妨问一句:

「如果我把这接口优化 200ms,能不能让这个组件彻底删掉?」

能删掉的圈圈,才是最好的圈圈。


那个转圈圈的东西,叫 throbber。

它见证了从拨号上网到光纤入户、从 CGI 到 Serverless、从沙漏到骨架屏的三十年。

它会继续转下去。只要还有请求要发、还有响应要等、还有带宽不够快、还有服务器在另一头转动风扇。

但我们写代码的目标,应该是让它少转一点、转得短一点、最好根本不转。

毕竟,没人喜欢看圈圈转。哪怕它叫得再好听——throbber,心跳的声音。

分享:

评论(0)

暂无评论,来写第一条吧~

发表评论