Connecting...

Cancel
抱歉,此頁面尚未翻譯。

Gyroscope的故事

流畅网页动画的10条原则

让你的网站做到60fps流畅动画的完整指南

流畅网页动画的10条原则

用CSS做出60fps动画的完整指南

自从去年我们上线Gyroscope以来,很多人问我们动画用的是哪个JavaScript库。我们想过把它开源,但魔法其实并不在那儿。

我们不希望大家觉得,非得依赖某个神奇的JavaScript插件才能解决这些问题。绝大部分时候,我们只是在吃浏览器性能、GPU和CSS3规范这几年进步的红利。

做出好动画没有银弹,只有花大量时间去测试和优化。不过,在多年折腾、反复撞上浏览器性能的天花板之后,我们总结出了一套设计 & 代码上的原则,照着做基本都能得到不错的动画。这些方法能让你的页面顺滑、在现代桌面和移动浏览器里都能跑,而且最重要的是,好维护。

每个人用的技术和实现方式都会有点不同,但这些通用的原则在几乎任何场景下都用得上。

什么是动画?

动画在互联网出现之前就有了,要把它做好,够你学一辈子。不过在互联网上做动画,有一些独特的限制和挑战。

要跑到流畅的60fps,每一帧的渲染必须在16ms之内完成!这时间可真不多,所以我们得找到非常高效的方式来渲染每一帧,才能保证流畅。

在网页上做动画有几十种方式。比如胶片式的做法,早在互联网之前就有了:把一帧帧略有差别的手绘画面每秒切换很多次,制造出动的错觉。

Twitter最近的新版爱心动画就用了这种简单的办法,在一张精灵图的各帧之间翻页。

这个效果本可以用一大堆小元素各自做动画来实现,或者做成SVG,但那样会复杂得没有必要,而且多半没这么顺。

很多情况下,你会想用CSS的transition属性,让元素在变化时自动做动画。这种手法也叫“tweening”,也就是在两个不同的值之过渡。它的好处是可以轻松取消或反向播放,不用自己写那一套逻辑。它非常适合“设好就不用管”的动画,比如入场序列,或者悬停这类简单交互。

延伸阅读:关于CSS Transitions你需要知道的一切

另一些情况下,基于关键帧的CSS animation属性可能更适合那些一直在跑的背景细节。比如Gyroscope logo里的圆环,就被安排成一直转。齿轮传动这类东西,也很适合用CSS animation的写法。

废话不多说,下面这些建议,但愿能大幅提升你的动画性能……

#1

除了opacity和transform,别改任何属性!

就算你觉得应该没事,也别改!

光是这一条基本原则,就能带你走完80%的路,移动端也一样。你多半听过这条,它不是什么新想法,但很少有人真照做。它相当于网页界的“吃得健康、多运动”:听着是好建议,你大概率会当没听见。

一旦习惯了这么想,其实挺直接的,但对习惯了给传统CSS属性做动画的人来说,这一步跨度不小。

比如说,你想让某个东西变小,可以用transform: scale(),而不是去改width。你想把它挪个位置,与其折腾margin或padding(那会让整个页面布局每一帧都重算一遍),不如直接用transform: translateX或者transform: translateY

为什么这样管用?

在人看来,改width、margin或者别的属性好像没什么大不了,甚至因为更简单还更顺手,但从计算机要干的活来说,它们是两个世界,其中一个糟糕得多。

浏览器团队在优化这些操作上下了很大功夫。transform非常容易做得高效,往往能直接用上你的显卡,不必重新渲染元素。

页面首次加载时你可以放开了玩:把所有角都做圆、用图片、到处加阴影,胆子再大点,甚至做个动态模糊。只发生这一次的话,多花几毫秒计算无所谓。但内容渲染完之后,你就不该再反复重算了。

延伸阅读:用translate移动元素(Paul Irish)

#2

把内容藏在明处。

用pointer-events: none加上opacity为0来隐藏元素

这条可能有一些跨浏览器的注意事项,但如果你只面向webkit和其他现代浏览器,它会让你的日子轻松很多。

很久以前,动画还得靠jQuery的animate()来做,淡入淡出之所以复杂,很大程度上是因为要在恰当的时机在display: none和block之间切换。切早了动画放不完,切晚了页面上又会盖着一层看不见的、透明度为0的内容。所有东西都得靠回调,在动画结束后去收尾。

CSS的pointer-events属性(它其实已经存在很久了,只是不常被用到)基本上是让元素不响应点击和交互,就像它根本不在那儿一样。它可以用CSS轻松开关,既不会打断动画,也完全不影响渲染和可见性。

把它和opacity为0搭配起来,效果基本等同于display: none,却不会因为触发新的渲染而拖累性能。要隐藏东西的时候,我一般就把opacity设成0、把pointer-events关掉,然后就可以把这个元素忘了,它自己会照顾好自己。

这招对绝对定位的元素尤其好用,因为你可以放心,它们对页面上其他任何东西都没有影响。

它还让你多出一点余地,时机不必卡得那么准:某个元素比它可见的时间多一秒钟可以点、或者多挡了一秒别的东西,天塌不下来;它要是完全淡入之后才变得可点,也没关系。

#3

别让所有东西同时做动画。

要编排,像编舞一样。

一个动画单独跑起来可能很顺,但和另外一堆同时跑,多半就乱套了。做一个跑得很顺的小demo,几乎什么效果都不难;但要在一个完整的站点上维持这个性能,难度要高一个数量级。所以,安排好它们的时序很重要。

你会想把时间点散开,别让所有东西在同一刻开始或同时在跑。一般来说,两三个东西同时动不会拖慢,尤其是它们的启动时间稍微错开的话。再多,你就有掉帧的风险了。

除非你的页面上真的只有一样东西,否则理解编舞这个概念很重要。它听起来像跳舞的术语,但对界面动画同样重要。东西要从正确的方向、在正确的时间进场。虽然它们各自独立,但应该让人觉得是同一个精心设计的整体的一部分。

Google的material design在这方面有一些有意思的建议。那不是唯一正确的做法,但值得你去想、去试。

延伸阅读:Google Material Design · Motion

#4

让transition的delay逐级递增,编排动作就变得很容易。

编排动画非常重要,也需要大量试验和测试才能调到对的感觉。不过,实现它的代码不必很复杂。

我通常只在父元素上(常常就是body)改一个class,触发一大批transition,每一个都有自己不同的transition-delay,在合适的时间进场。从代码角度你只需要关心一次状态变化,不用在JavaScript里维护几十个时间点。

Gyroscope Chrome扩展里的动画

把一串元素错开进场,是编排元素既简单又容易的办法。它很有力量,因为在好看的同时还给你省下了宝贵的性能,记住,你希望同一时刻只有几样东西在动。你要把它们散得够开,让每一个都跑得顺,但也别散得太开,让整体显得拖沓。重叠的部分要够多,让它像一段连续的流动,而不是一串各自为政的动作。

代码示例

让元素错开有几种简单的办法,尤其是面对一长串东西的时候。如果不到10个,或者数量非常可预期(比如静态页面),我一般直接在CSS里写死这些值。这最简单,也最好维护。

一个简单的SASS循环

如果列表更长,或者内容非常动态,可以遍历每一项来动态设置时间。

一个简单的javascript循环

通常有两个变量:基础延迟,以及每一项之间的间隔。这个平衡很难找,但一旦调到那组对的数字,感觉就会刚刚好。

#5

用一个全局倍数,在慢动作里做设计

之后再把一切加速回去。

做动画设计,时间就是一切。20%的工夫花在把东西实现出来,另外80%花在找到对的参数 & 时长,让一切同步、顺滑。

尤其是在编排多个元素、想从页面里榨出性能和并发的时候,把整件事放慢来看会容易得多。

不管你用的是Javascript,还是SASS这类CSS预处理器(我们很喜欢它),多做一点数学、用变量来搭,都应该相当直接。

你要保证换个速度或时长很方便试。比如说,如果一个动画连放慢到1/10都还在卡,那可能有根本性的问题。如果拉长50倍之后跑得很顺,那就只是去找它能跑的最快速度而已。全速下5毫秒的问题可能很难察觉,但整体放慢之后,它们会变得无比明显。

尤其是面对特别复杂的动画,或者要啃下棘手的性能瓶颈时,能看到慢动作真的很有用。

核心想法是:在放慢的状态下把大量细节做到位,然后把整体加速,让它感觉刚刚好。这些差别会很微妙,但用户能感受到那份顺滑和讲究。

这个功能其实是OS X自带的:按住shift点最小化按钮或应用图标,你就能看到慢动作的动画。我们甚至一度在Gyroscope上也做了这个慢动作功能,按下shift就会生效。

#6

把你的界面录成视频再回放,能拿到很宝贵的第三人称视角。

有时候换个视角能让你看得更清楚,而视频是很好的办法。

有些人先在After Effects里做一段视频,再想办法在站点上实现出来。我常常是反过来的:想办法用站点的界面做出一段好看的视频。

能把一样东西发成Vine*或视频,其实是个挺高的门槛。有一天我对自己做的东西挺兴奋,就想录一段发给几个朋友。

结果我回头一看,发现一堆不太行的地方。有一次明显的卡顿,所有时间点都差了那么一点。我看得有点尴尬,没发出去,反而意识到还有很多活要干。

实时用的时候,这些很容易被一眼带过;但把动画放到视频里,反复看,或者慢放看,任何问题都会变得极其扎眼。

人们说镜头会让你胖10磅。也许它还会让你多出10帧。

现在,看自己页面的慢动作视频、发现哪一帧不对就去改,已经成了我工作流里重要的一环。把锅甩给浏览器慢很容易,但多做一些优化和测试,这些问题其实都能一个个解决掉。

等到你不再因为视频里被抓到卡顿而难为情,觉得这段视频拿出去分享也没问题了,那这个页面大概就可以发布了。

#7

网络活动会造成卡顿。

大的HTTP请求要么预加载,要么往后延

图片是这里的头号元凶,不管是几张很大的(比如一张大背景图),还是一堆小的(想想50个头像同时在加载),或者干脆内容就很多(一个长页面,图片一路铺到页脚)。

页面刚加载的时候,有一大堆东西在初始化、在下载。再加上统计、广告和其他第三方脚本,情况更糟。有时候,把所有动画在加载后推迟几百毫秒,对性能就有奇效。

这条不到必要的时候别过度优化,但一个复杂的页面,可能真的需要把内容的延迟和时序算得很精确才跑得顺。总的原则是:一开始尽量少加载数据,等重活和入场动画做完,再接着加载页面其余的部分。

在数据很多的页面上,把一切加载完的开销可能相当可观。一个在静态内容下跑得挺好的动画,一旦同时开始灌入真实数据,就可能崩掉。如果某个东西按理说应该没问题,或者有时顺、有时不顺,我建议去查一下网络活动,确认你没在同一时间干别的事。

#8

别把动画直接绑到滚动上。

听着挺酷,其实真不怎么样。

基于滚动的动画这几年很火,尤其是带视差或者别的特效的那种。它们算不算好设计可以另说,但在技术实现上确实有更好和更差的做法。

这类做法里性能还过得去的一种,是把滚动到某个距离当成一个事件,只触发一次。除非你真的很清楚自己在干什么,我建议避开这一类,因为太容易出问题,也真的很难维护。

更糟的是不用默认滚动条、自己造一套滚动逻辑,也就是所谓的滚动劫持。求你别这么干。

这条规则在移动端尤其有用,但从理想的用户体验来说,大概也是个好习惯。

如果你确实想做某种围绕滚动或特殊事件的体验,我建议先快速做个原型,确认它性能扛得住,再花大力气去设计。

#9

尽早 & 经常在手机上测试。

大多数网站是在电脑上做的,也多半就在做它的那台机器上测得最多。于是移动端的体验 & 动画性能常常成了事后才想起的事。有些技术(比如canvas)或者动画手法,在手机上未必跑得一样好。

不过,只要写法 & 优化到位(见第1条),移动端的体验甚至可以比电脑上更顺。移动端优化曾经是个很棘手的话题,但现在的新iPhone比大多数笔记本还快!如果你一路照着前面的建议做,很可能开箱就能有很好的移动性能。

对几乎任何站点来说,移动端的使用量都会占很大一块,而且非常重要。这听起来可能有点极端,但我建议你整整一周只用手机看它。被迫用移动版不该像受罚,可事实往往就是。

不停地改设计 & 提性能,直到它跟站点的大屏版一样精致、一样顺手。

如果你逼自己一周只用移动版,最后你多半会把它优化得比大版还好用。虽然天天用着窝火,但这值得,因为这意味着问题在用户遇到之前就被修掉了!

#10

经常在多种设备上测试

屏幕尺寸、像素密度或设备本身,都可能带来很大影响

除了手机和桌面之分,还有很多因素会大幅影响性能,比如屏幕是不是“retina”、窗口的总像素数、硬件有多老,等等。

虽然Chrome和Safari都是基于Webkit、语法也相近,但它们各有各的怪脾气。Chrome每次更新都可能修好一些东西、又带来新的bug,所以你得一直绷着。

当然,你也不会只按最低配来做,所以找到一些聪明的办法,把增强效果渐进地加上或去掉,会非常有用。

我常在自己那台小小的MacBook Air和大大的iMac之间来回切,每切换一轮都能发现一些小问题和可以改进的地方,尤其是动画性能方面,但也包括整体设计、信息密度、可读性等等。

媒体查询是应对这些不同情况的利器:按高度或宽度分别设样式是最常见的用法,但它们也可以用来按像素密度或其他属性做定向。判断出操作系统和设备类型也很有用,因为移动端的性能特点跟电脑可以差很多。

希望这些方法能在你下一个项目里派上用场。祝好运!