防止微信内出现全文翻译提示。因此在这里放一个充满中文的分区。 防止微信内出现全文翻译提示。因此在这里放一个充满中文的分区。 防止微信内出现全文翻译提示。因此在这里放一个充满中文的分区。 防止微信内出现全文翻译提示。因此在这里放一个充满中文的分区。
2025年1月15日@Aragakey.

制作流畅界面

在 2025 年 1 月,我与设计师完成了两个项目,它们分别是 视频号互选创作者榜朋友圈广告年度评选。我们共同完成了一些有趣的交互动画,其中包含了我对这个领域的理解与追求。

这篇文章的标题是《制作流畅界面》,来自于 WWDC18《设计流畅界面 Designing Fluid Interfaces》。这个 7 年前的视频对我影响极大,我看过它许多遍、将它视为指引的灯塔。接下来,我将通过几个具体案例,分享自己是如何学习与制作流畅界面的。希望这个对我职业发展有重要意义的视频,也能让你觉得有所收获。

我们从最基本的开始。

实时与连续反馈

流畅界面的基础,是保证实时的反馈。

人们对延迟非常敏感。当发生任何时延迟时,就会感到脱节,交互不再像是自己手指的延伸。

—— 设计流畅界面 05:47

实时的反馈

在上面的案例中,随着手指的向下滑动,首张卡片会向下移动,整叠卡片也会实时响应。一般来说,完全的实时是没有问题的,不过在这个案例中还可以优化。

正是因为我们需要首张卡片尽快地向下移动,它的每次 Y 轴位移距离就可能比较大,可能接近 10px。在上面 60 帧的视频中你可以看到这种跳跃的变化 —— 实时的变化不一定让人感到连续

// 实时变化的值
const rotate = useMotionValue(0)

// 连续变化的值
const rotate = useSpring(0, {
  visualDuration: 0.1,
  bounce: 0,
})
// 实时变化的值
const rotate = useMotionValue(0)

// 连续变化的值
const rotate = useSpring(0, {
  visualDuration: 0.1,
  bounce: 0,
})

我们可以将实时变化的值,变成一个硬度(stiffness)非常强的弹簧动画,你可以理解为现在这根弹簧“冲击力”极强,曲线会是这样:

这会让变化更加连续,使每一次的变化在 5px 左右。这样当然会同时引入一点延迟,但只是不会感知到的一点点:

加入延迟,更加连续

现在,我们就实现了一个实时且连续的反馈。

可打断,可反悔

上面所说的实时的反馈看起来理所当然,但如果结合 UI 的各种状态仍要做到实时的话,事情就会开始变得复杂。

我们发现,让界面始终具有响应性、始终理解用户是非常重要的。这对用户的预期和对界面的理解来说非常关键。这样用户才能感到舒适,并意识到每当需要时,界面都会及时响应。

—— 设计流畅界面 09:44

在 Apple iOS 的例子中,当用户打开一个 app 时,他可以在过渡过程中就选择关闭它。用户不需要等待任何过渡的完成和停止。这也是交互动画与视觉(非交互)动画的一大区别 —— 过渡不只是一种形式。

这意味着,所有的动画都要关注:播放到一半,滑走会发生什么;以及滑走后,马上又回来,怎么办?繁琐的工作就在这里,因为我们的转盘涉及到元素和背景的变化。

比如,在去年的转盘入场过程中,用户不能打断它,它只是一种形式。而今年,用户可以在任何时候决定如何开始:

在任何时候开始交互

根据统计结果,接近一半(45.71%)的用户不会等到入场结束再操作。


转盘还在转动就能投票;不需要等待投票动画结束,不想看就滑走:

不需要等待动画结束

根据统计结果,有 36.77% 的用户在转盘还在转动时进行投票。


视频号互选创作者榜 中也一样,你随时可以点开卡片查看更多信息,也可以在卡片翻转的过程中选择关闭它:

保持对操作的响应

流畅的界面要始终具有响应性,可打断、可反悔。允许用户在每一个状态变化的过程中,都可以将他的手指按下,界面随他调度。

响应速度,保持惯性,奖励动量

当用户松手时,流畅的界面应该响应速度,保持惯性。有的时候还需要奖励动量(momentum),最常见的就是橡皮筋效果(rubberband)的例子。

从一个短暂、轻量的交互开始,通过位置、速度、加速、力量,放大你移动的结果,让你仍觉得是自己的延伸。通过轻量的交互,就能得到愉悦的感受。

—— 设计流畅界面 15:06

响应速度,保持惯性

以上的两种交互的表现是类似的,都围绕着 “松开手之后,应该停在哪?”“停到那个地方,要花多久?” 这两个关键问题展开。过去已经讨论过,在这篇文章中不再展开。


奖励动量发生在下面的场景:当用户点击左右两侧卡片时,转盘也会转动,但是这个过程不会有回弹效果;而当用户滑动后,转盘就会带有一个回弹,这就是一种对动量的奖励,也更用户明确:已经到达最终位置了,你可以继续交互了。

奖励动量

这样的细节我觉得是合理的,是有趣的。

操作与想法并行

流畅的界面还可以通过让操作与想法并行,让用户不需要在决策前花费额外的时间。

image

线性的界面迫使用户先思考、再做决定、然后交互。而流畅的界面思考和手势是同时发生的,这比先思考再交互要快得多。

—— 设计流畅界面 08:24

我们对此也做了一些有意思的尝试,虽然我觉得确实有一些“牵强附会”。

在用户点击右上角 Tab 进行切换时,这叠卡片会做一个单方向的 Shuffle 动画。而如果用户不抬起手指,它则会一直保持这个状态。你不需要着急,也可以上下拖动,最后决定好了再松手:

点击与滑动结合交互

从统计结果上来看也很少有用户这样使用,有 2% 的用户会交互 2 秒及以上。但是这种尝试是有意义的,因为它赋予了交互以可探索性。当某个用户发现了这个功能,他或许会觉得有趣。而当我们在实现了上面的实时、连续、可打断、响应速度、惯性...这些所有特性之后,最后加上这个功能,这一叠卡就突然变得更完整。不,不应该说是完整,而是就 更像一叠卡 了。

好,我们可以接着往下问,为什么就要真的像一叠卡呢?

视觉与交互不可分割

image

在《设计流畅界面》的最后一部分,Apple 的设计师要将流畅性作为一种媒介(Fluidity as a Medium)。“媒介”这个词虽然不太日常,但我们又不能简单地说:把流畅性作为设计目标。因为这体现不出“媒介”这个词的妙。媒介:能使人与人(人与事物或事物与事物)之间产生联系的物质。

第一次看到上面这一页的内容时,我佩服 Apple 的设计师。我看过蛮多 Interface Guidelines 或者组件库设计原则,它们通常会告诉你多少秒算快、多少秒算慢,它们列举 ease-in、ease-out,它们说动效要明确、清晰、流畅、自然、理解、聚焦、共情... 不知从哪天开始,我觉得我懂了,我不需要再听这些原则了,我学够了,我已经有感受了,我知道如何分辨了。

—— 只是,当我想要实现这些我都懂了的体验时,我好像没有设立一个实际的目标。

Apple 的设计师说,流畅本身就是传达信息的媒介,要把流畅与否作为设计目标。除了文本、图片、视频这些媒介,除开这些“看得见”的,还要有“摸得着”的目标。这是我没有听到过的。

视觉与交互不可分割,当一叠卡摆在面前时,它最好就是一叠卡;当一个转盘摆在面前时,它最好就是一个转盘。如果用户觉得你的设计看起来可以这样交互,那么你最好满足他,否则你就不要长那样 —— 说起来又像是理所当然的废话了。

打造符合产品的品质

当你真正地完成了一个流畅的界面,你就会发现它很难被直接复制,因为它体现了对所有细节的理解。甚至,能赋予产品独特的“性格”。

在评选选择标准的列表上,我加入了类似 Apple Message app 的列表滚动效果。每个元素不再是以一个整体的形式滚动,而是各自有一些细微的错落:

细微的错落

我曾在《交互动画的层级设计》中探讨过这种形式。错落的形式并不妨碍我们很自然地将它们视为一个整体。可是,为什么要这样做呢?

  1. 这种细微的不同步,避免了列表像“死板的整块内容”那样滚动,每一个标准都独立存在,每一个标准都可以被你点击、定义,这是功能上的意义;
  2. 你每天都在滑动同样的长列表,在微信里、在小红书里。只要你开始在这里滑动,你就能体会到这种细微细腻的差别,我希望它们的错落让你觉得自然舒适,又和你再熟悉不过的体验有一点点不同。优雅而精致,一直是我在体验上的目标。

这里的具体做法是将每一个元素的位移都分开计算。

  1. 当用户向上滑动时,将当前正处于页面顶部的元素标记为第一个,然后依次对在它之后的元素调整更小的硬度(stiffness)和更高的阻尼(damping);
  2. 当用户向下滑动时,将当前正处于页面底部的元素标记为第一个,然后依次对在它之前的元素调整更小的硬度(stiffness)和更高的阻尼(damping)。
// 这几个数字的调整花了我不少时间
{
  tension: 1200 - deltaIndex * 15,
  friction: 26 + deltaIndex * 5,
  clamp: true,
}
// 这几个数字的调整花了我不少时间
{
  tension: 1200 - deltaIndex * 15,
  friction: 26 + deltaIndex * 5,
  clamp: true,
}

把具体的数字打出来看看:

连续变换的弹簧参数

比如当用户向上滑动时,页面顶部的元素就会连续地变换到 tension: 1200, friction: 26,而页面底部的元素则会连续地变换到 tension: 1035, friction: 81。就是因为整个过程都在 连续不停地变换,整个滚动过程才顺畅。它们两个(即头尾元素)的差异换算成时间大概是 0.15s0.2s

image

但时间和参数的数字并没有用(时间只是物体运动的结果,而非原因),还是需要拿在手上不断调整。比如在惯性滚动时,我将 timeConstant 调整为 150,这会导致衰减得更快,列表停下来就更快,细看就会发现这点 —— 因为这里的需求并不是浏览长列表的效率。不展开了。

也可以在下面的 Demo 中粗略体验,调节一下参数试试:

唤醒感官
73k
眼前一亮
73k
有互动
73k
贴近生活
73k
诚实
73k
匹配场景
73k
推得准
73k
善良
73k
超越时间
73k
73k
引发思考
73k
有观点
73k
够深刻
73k
有洞察
73k
反映现实
73k
有激情
73k
破格
73k
满足幻想
73k
有故事
73k
直白
73k
学到东西
73k
主题明确
73k
有格局
73k
说人话
73k
破圈
73k
结合热点
73k
优雅
73k
简约
73k
品味独特
73k
有对的人
73k
有共鸣
73k
有情怀
73k
打动我
73k
巧妙
73k
有趣
73k
有温度
73k
愉悦
73k
Wow
73k
引发想象
73k
记得住
73k

交互动画,就是为了“好玩”

如果我们再把话题往上拔,当你掌握了制作流畅界面的所有技巧,当你赋予了产品品质。流畅界面的最后是什么?这场分享还有一句令我佩服的话:

好玩,是流畅界面的自然结果。

—— 设计流畅界面 01:00:28

image

也许交互动画的最终目标,就是好玩。好玩因为界面永远响应你,好玩因为你有兴趣探索可能的交互。好玩是流畅界面的自然结果。

而我还在不断精进、不断更深入地理解这些话。

做一个实践者

Apple 的设计师能说出这样“独特”的话,让我感叹他们果然是优秀的实践者。我也同样在实践与尝试中试图理解这些话。只有动手做了,才知道实现一个看起来简单的动画有多少容易忽略的细节。

作为实践者,我还需要关注性能。比如,今年对整个转盘做了 React 的 render 优化,实际在转动时只有 5 张卡片:

渲染优化

这一叠卡就是 6 张更换位置,这样实现也最方便:

渲染优化

以及渲染 40 个标准列表也只会让屏幕内的元素显示,成为了那个页面中最流畅的交互动画。当然这些都不困难,是应该做到的。

还有在合适的地方加入 will-change: transform,它对安卓还真的有用;还有不要以为 svg 方便就是万能的就乱切,svg 里面的 filter 会造成卡顿;linear-gradient 在低版本 Safari 也会有问题...

这两个项目中的大小问题,我不会在这篇文章中再展开。还有数字动画、投票动画也令我挺满意,但都不是这篇文章的主题。因此我将我遇到的问题和经验另外集合写了一篇《Web 流畅界面建议》。

最后

交互动画这个领域,属于我们做 UI 的工程师。

  1. 如果光说动画、如果只是在屏幕上播放一下,那么谁做都一样。设计师用 AE 一般更适合,开发者不过就是播放一下而已。但加上交互、动画需要响应用户的操作时,就不适合了;
  2. 如果光说功能,每一个交互动画核心代码不过二三十行,复杂度、通用性都不高,为了方便我也会基于不同的开源库实现。但这样的反向推导意义有限,并不意味着过程就容易。
    即便我已经做完了这些,还是很难用一两句话清晰地说明具体是怎样做的。我不想故弄玄虚,或者把事情搞得复杂显得自己牛逼。我明白我还是在重复 Apple 7 年前就做到的事,但我总是需要从头开始说,从手指按下的那一下开始说。

image

想对各位设计师朋友说:

不知道大家有没有过这种时候:当你用几句话也很难描述自己理想的体验,或者在 Figma 里通过标注还是觉得很难形容,然后发现自己从前读过的动效规范帮不了你更多。那么恭喜你,你可能正站在一扇门面前。这扇门的背后非常有趣。

接下来你需要拿起你的手机坐到你的工程师面前,你要把这个视频发给他,你要跟他说我们要做的事比拉一条贝塞尔曲线有趣得多。要花更多的时间去做快速验证。最重要地,要把你的想法转换为开发者自己的想法。你们要做好玩的事。对,只是做流畅的界面而已。