现代 CSS

渐进式动画解决方案

特别声明:如果您喜欢小站的内容,可以点击申请会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!或添加QQ:874472854(^_^)

今天聊的内容是淘宝虚拟互动实验室的@渚薰大神 在2017年06月在北京GMTC大会上分享的一个主题。未能亲临听到相关的精彩分享,但还算是有幸的,在内部听到@渚薰大神 的分享。个人对Web动画这方面的课程非常的感兴趣,而且现在和团队一直在致力于手淘互动动效相关的研究。经历了从Gif、视频到CSS Animation的零至一的过程,并且致力于JavaScript驱动的动效开发,以及现在致力于研究的数据化驱动的动效。这样的一个过程是幸福的,而且也是具有挑战力的。我想很多喜欢动画的同学也对这样的一个课程会感兴趣,所以接下来,我们根据@渚薰大神分享的PPT的思路来聊聊渐进式动画解决方案

PWA

最早听到PWAProgressive Web Apps)这样的一个概念是2016年在上海Qcon的分享会上听到@黄弦 大大的分享,但我们今天要聊的并不是Progressive Web APPs,而是其变身Progressive Web Animation。我们把其称为渐进式动画。那问题来了,什么才是渐进式动画呢?这是值得每位爱好动画的同学去思考的?为了更好的阐述自己对渐进式动画的理解,将分几个方向来介绍:

  • 重新认识动画
  • 如何操作动画
  • 如何管理动画
  • 如何制作动画
  • 重新思考动画

接下来一一聊聊。

重新认识动画

要重新认识动画,那么其中有两个非常重要的概念:动效插值

动效

动效是源于电影动效。动效是录音学范畴,简言之,指各种动作的声音效果,又被称为拟声。比如武打片里,骨折的声音是芹菜制造的,下雨的声音是由洗脸的声音制造的。

看上并不是我们所要说的动效,这并不要紧。如果需要和我们的实际工作中紧密联合在一起,我们的Web也可以有音效,特别是早期的Flash,各种的配合音效,得到与众不同的效果。随着CSS和HTML以及JavaScript相关技术不断的革新,同时各种特性能得到众多现代浏览器的支持。现在的Web展示形式也不断的变化,不管是Web页面或者说APP应用,都会或多或少的添加一些动效效果。

俗话说得好,颜值不够,动效来凑,Web动效已经不仅仅是Web设计的润滑剂了,它的功能更多的体现在交互逻辑、视觉渲染和创新实践上,上能引人注目,下能潜移默化。对于Web开发人员而言,更喜欢把这些动效称之为Web动画。为了接地气,后面也将称之为动画。

迪士尼动画大师乃特维克的毕生经验对Web动画浓缩成一句话:

动画的一切皆在于时间点和空间幅度!

事实上,动效设计和做动画是一脉相通的,我们不要做写实主义的动画,而是要通过时间点和空间幅度的设置为用户建立运动的可信度。这样描述感觉有点绕。其实我们可以这样来描述动画的概念:

动画是指由许多帧静止的画面,以一定的速度(比如每秒16张)连续播放时,肉眼因视觉残象产生错觉,而误以为画面活动的作品。为了得到活动的画面,第个画面之间都会有细微的改变。

其实我更喜欢说动画就是以时间置换空间。比如下图:

图形A状态(一个正方形)经过时间的变换到了图形B的状态(一个圆形)。从正方形变成圆形这样的一个过程就是一个动画效果(动效)。当然为了制作这样的一个动画,会添加控制动画的一些手段,比如控制持续时间、延迟时间、缓动函数、动画次数和方向之类的等。当然根据不同的制作动画的方式,这些控制动画的参数或者说手段吧,其方法不一样。这里暂且不深入的介绍。

刚才也说过了,这样的一个过程就是一个动画效果,专业一点称之为Motion Effect。而这个Motion Effect又受众多因素的影响。同样拿上面的示例来说,正方形是一下子变成圆形呢还是慢慢的变形圆形呢?又是经过多少时间才变成圆形呢?这一切的一切都会影响到。而其中有一个较好的方案就是在运动的过程中将插值引进来。

插值

这里所说的插值其实是指线性插值。线性插值是数学、计算机图形学等领域广泛的一种简单插值方法。在平常实际运用当中,把插值称之为lerp,简单而言:

lerp是两点之间的线性插值的别称。

同样的回到面的示例,正方形状态A是一个点,圆形状态B是一个点。如果我们把状态A称为0点,把状态B称为1点。那么在这个过程中我们可以有很多个值,比如0.10.2....9之类,比如下图所示:

01这样的一个过程就是一个插值过程。用到图形变化中来,比如正方形是四个点连起的图形,那么变成圆形,这个点数越多这个圆就越圆,比如163264128等等,也可以说点数越多,越趋向于圆。而这个程,我们使用插值原理来计算,效果会更佳。比如下面两个图:

一个高级动画或者动画艺术家都手绘的关键帧定义的一个动画。不管你是怎么一个高级的动画设计师,手绘总是有所限制的,如果我们换成插值来绘制:

插值能增加很多中间帧,让动效变得更为丝滑。

上面演示的是帧之间的插值,事实上我们除了这个之外,还有颜色的插值,图形插值等等。这里想要表达的观点是:插值能让动效变得更为丝滑。有关于插值方面的相关介绍,可以阅读:

如何操作动画

操作一个动画,其实具有两个过程,其一制作动画的场景,其二拼接动画场景。把它们结合起来就是操作一个动画。那么操作一个动画,就目前的技术常见的有:

  • CSS Animation
  • SVG Animation
  • JavaScript-Driven Animation

我想大家最早接触动画的控制方式应该类似于jQuery的.animate():

$( "#clickme" ).click(function() {
    $( "#book" ).animate({
        opacity: 0.25,
        left: "+=50",
        height: "toggle"
    }, 5000, function() {
        // Animation complete.
    });
});

对于这种制作动画的方式不做过多的评介。但随着CSS技术的发展,使用纯CSS就可以很好的实现Animation。在CSS中主要是使用@keyframesanimation-*属性来控制动画。CSS Animation虽然实现一个动效较为容易,成本也较低,但对于一些复杂的动画场景,他就变得非常的鸡肋。除此之外,这两年SVG Animation也越来越多在实际场景中使用。

不过,这里需要特别提出的是JavaScript-Driven Animation,也就是JavaScript驱动动画。最简单的就是W3C规范提供的Web Animation API简称为WAAPI。其原理非常类似于CSS Animation,具有@keyframes的概念,也具有类似于animation-*属性样的方法,用于控制动画。同时还提供了对应的Timeline的概念,而Timeline又是动画中一个至关重要的一个概念,稍后或多或少会介绍一些。

虽然有了WAAPI,可以使用原生的JavaScript制作动画,但还是有一些功能性的限制,而且实现方式,制作流程等等,都有待于改善。但对于Web社区而言,这一切都不是问题。因为很多开发者,都会针对于自己的业务,或者说自己的需求,在某一方面做出优秀的成绩,并且分享给社区。正因为如此,社区中有很多使用JavaScript来驱动动画的库,比如:GreenSockCreateJSAnimeMotion等:

不管是哪种制作动画的JavaScript库或者说工具都有各自的利弊。只能说选择最适合自己的,俗话说:

站在巨人的肩膀上,看得比别人更远些。(If I have been able to see further, it was only because I stood on the shoulders of giants.)

除了上述制作动画之外,我们还可以采用Canvas和WebGL。特别是WebGL,在当今算是一个热门的话题,国内外很多团队都在研究这方面的技术,其中最具代表性的作品就是ThreeJS

制作动画另一个不可避免的问题,那就是动画的性能问题。这是一个很深的、很复杂的话题。不过我们可以借助一些其他的工具帮助我们在底层做一些变化,比如采用GPU来渲染:

  • GPU.JS:使用JavaScript给GPU加速
  • PixiJS:一个HTML5引擎,也是最灵活的2D WebGL渲染器

除了借助工具之外,我们也可以在自己的代码层面做一些优化,比如使用requestAnimationFrame这个API:

const raf = requestAnimationFrame;
const running = [];
const idle = [];

export const add = tick => running.push(tick);

export const remove = id => (running[id] = idle[id] = undefined);

export const run = () => raf(function tok() {
    raf(tok);
    running.forEach(t => t());
});

export const pause = id => (idle[id] = running[id]) && (running[id] = undefined);

export const resume = id => (running[id] = idle[id]) && (idle[id] = undefined);

有关于这方面的介绍大家可以阅读下面几篇文章:

以前在站上也整理了一些优化动画性能的相关文章,对这方面感兴趣的同学可以阅读下面的文章:

如何管理动画

制作出来动画,我们可以将整体的动画细分出来,那么怎么管理动画变得就很重要了。我常常喜欢把动画比喻成一场舞台剧,整个剧会有演员,演员什么时候出场之类的。而控制演员出场顺序和时间之类的由导演来负责。回到我们的动画中来,我们可以把整个动画当作一个舞台剧(一个故事),故事中有很多个片段(也就是我们的动画分场景),而串起整个动画是由时间轴来控制,常常称之为Timeline。

比如:

正如上图所示,这里有三辆小汽车向右行驶的动效,那么蓝、黄、红怎么出现,就由时间轴来控制了。而整个过程,它其实也是一个流式的过程。有接触过Flash的同学应该知道。在Flash中有两种动画方式:

  • 流式动画
  • 交互式动画

所谓流式动画,就是像电影那样按既定顺序一直播放,不会停,也不会发生改变的动画。你在电视上看到的所有动画片都可以说是流式动画。流式动画最显著的特点就是你无法对流式动画施加影响,比如你无法期待单击流式动画中的某个器物(动画元素)而指望因此而发生什么事情。总之,流式动画中所有的一切都是注定的,有一种宿命的感觉。尽管流式动画的能力有限,但不可否认,流式动画的应用还是非常广泛的。

说到这里,大家可能会想到,我们CSS Animation。众所周知,一旦我们定制好了一个CSS Animation,我们是无法通过一些事情让动画中的某些动画元素做其他的事情。这也是令我们头痛的地方之一。比如我们做一个互动的动画,希望动画播放到一定的位置,弹一个弹框出来,告诉用户你可以领起红包了。

除了流式动画,还有一种交互式动画。所谓交互式动画,简单地说就是动画随时准备因你的命令或某些事件的出现而发生变化。例如,当你用鼠标点击动画中一只小狗的头时,它会高兴地冲你摇尾巴。在这里,鼠标的单击就是一个命令(或者说是一个事件),而小狗摇尾巴就是对这个命令(或说事件)的响应,整个过程就可以被看作是一次交互。

到目前为止,仅使用CSS Animation,或者后面将提到的一些制作动画的IDE或软件,他们做出来的动画都是流式动画,只能通过时间轴来控制动画,整个动画中没有任何可交互性而言,这对于大多数业务场景是无法达到需求方的要求。但对于JavaScript驱动的动画而言,我们的交互行为就要丰富的多,比如SVG Animation,Canvas Animation等。

感觉有点跑题了,这一节我们要说的是管理动画,其实除了管理动画中的交互行为之外,更为重要的是管理动画的播放。前面也提到过了,不管是CSS Animation动画,还是JavaScript驱动的动画,都是通过Timeline来管理。只不过JavaScript更具优势。来看两个小场景。

先来看CSS Animation中的Timeline,如下图所示:

上图演示的是一个红包火山的动画场景(喜欢手淘的同学,应该在去年的双十一看到过这样的动画),在这个动画中我们包括了火山升起(valcaoRising)、火焰柱喷发(expulsion)、岩浆流动(flow)和红包喷发(blowout)。在这个动画中,我们要人工的去计算动画的延迟时间(animation-delay)和持续时间(animation-duration)。比如火山升起,我们花费了1s,火焰柱喷发等火山升起才开始,这样一来,其延迟时间为1s,并且持续了.4s,在此同时岩浆流动的效果也同时发生,也就是说岩浆流动的延迟时间也是1s,而其持续了2s,其实动画进行到1.4s时,火焰柱喷发的动效是已经停止了。除此之外,还有红包喷发的一个动画,而这个动画是在火焰柱喷发结束的时候开始,这样其延迟时间是1.4s,并且持续了15s。其实整个动效花了16.4s

如果这样做就能满足业务方的需求,那是开发者的福音,但是要变动某个时间,比如火山升起需要持续2s,那么后续的三个动画的时间都需要变更。而这个过程是一个痛苦的过程。

但在JavaScript驱动的动画中,我们依旧是通过Timeline来管理动画,但会变得轻松和灵活的多,比如下图:

在JavaScript中,我们可以通过技术手段,将相关功能封装成函数,然后在动画流中调用:

add(me) => (
    playAt: () => number
) => assign(me, {playAt});

tick(elapsed) => queue.forEach(me => {
    if (me.playAt() >= elapsed && !me.started)
        me.start();
});

同样拿上图来说,如果我给火山升起通过playAt()来得到起始时间,并且函数能拿到其停止时间,并且在其停止的这个时候通知其他动画什么时候开始播放。这样我们控制动画会变得轻松的多。哪怕是我们需要改变其中某一个或某两个的时间,也可以很好的自动获取。

() => me1.finished;

let fetched;
fetch(url).then(() => fetched = true);
() => fetched;

() => store.getState() === ‘BTN_CLICKED’;

() => me2.started && (me2.startAt + 500);

简单点讲,我们把动画的控制、决定和管理都交互两个函数,比如playAt()stopAt。比如下图:

这样是不是感觉爽得多了。

如何制作动画

其实前面我们简单的提到了如何制作动画,比如通过CSS Animation、SVG Animation或者说JavaScript。但在这一节,我们继续来聊这个话题。在具体聊这个话题之前,咱们先来看一下开发者和设计者之间的合作关系。

早期的Gif动画、视频动画或者说Flash动画,这些可以说都不需要开发人员去做什么(这些都是流式动画,无交互可言)。但随着市场的变化或者说技术革新后,开发人员想尝试着新的技术来实现动画,并且在提供给业务方,这样做多NB,跟老板说,这样能节约多少成本。没想到,这个时候自己挖了一个巨坑让自己和自己的小伙伴跳进坑中。

别的不说,咱先上一张图:

可以说这是一个痛苦的过程,视觉设计师将想要的动效制作成视频文件或者Gif文件以及可切图的PSD文件给开发人员,然后开发人员将Gif动效或者说视频演示的动画转成Web动画。大家可能会问,这不是蛋疼?为什么需要制作视频或Gif文件呢?提供一个PSD文件不就行了?其实视觉设计师也是这么认为的。但话说回来,有多少开发人员能不看效果,通过视觉设计师或者说业务方的描述就能理解所需的动效呢?不知道你行不行,反正我经历之后告诉我,我是不行。

这样的一个过程,其实也增加了设计师和开发者的沟通成本,也增加了设计师的工作量。特别是当需求变更之后,设计师欲哭无泪。这个时候老板会说,成本、成本、效率、效率。设计师会说麻烦、麻烦。需求方会说,就这样吧,其实离我需要的还是有差距的。

随着时间变化,不管是自己的Boss还是需求方,都希望有所改变:动效能不能变得更丝滑一些、效果还原度能不能更接近一些、能不能更有一些创意和创新,能不能玩一些更有意思的...

开发人员心中千万个草昵玛就出来了。但活还是要继续做的,只是换个方式做而以。这也是我和我们团队小伙伴近一年多来一直在研究的一个课程:可量化和数据驱动

这个流程让我们有了更多的变化。视觉设计师专业的做他的设计,开发人员专业的做他的业务开发。为什么这样说呢?因为动效的转换我们通过工具或者软件来实现,然使用JavaScript的将业务插入到转化后的动效中。

简单讲,我们的开发过程变成了这样:

其实这样的过程,并不是我们独家有的,最早的时候有些团队使用Animation CC设计动画,然后直接转换出Web动画,它依赖于CreateJS这个库。另外也有团队使用After Effects CC制作动画,然后借助bodymovin将动画转出来。其中最具代表性的就是Airbnb的Lottie:

有关于这方面的DEMO,可以点击这里查阅

这是别人家的产品,也是巨人的肩膀。其实在Lottie和Bodymovin还没有开源出来的时候,我们团队也有一个类似的产品,AFT(Animation Flow Tools)。AFT没有开源,我不能聊得太多,简单的说,AFT具有自己的渲染引擎、也可以借用别人的渲染引擎,比如Canvas、Weex之类。但其主要功能和特点是用来管理动画,扩展动画。而且AFT也具有类似Lottie的特性,只不过我们的Player层是自己写的。下面的示例就是使用AFT通过AE转出来的一个动效,并且在实际项目中运用的:

我们把这样制作动画称之为可量化和数据驱动。主要原理其实在上面也提到过了。这里简单的复述一下:

通过类似AE这样的制作动画的软件,将动画导出成一份JSON数据,这份JSON数据将描述动画。同时使用Player层结合DSL,将JSON数据和业务逻辑整合,完成带有可交互的动画。

虽然我们初步完成了这些样的一个开发模式,但路还很长,我们也在不断努力。希望有一天能让她面世,与众多动画爱好者共享。

重新思考动画

经过这一系列的变化,并且随着技术越来越先进。我们有必要重新思考动画。除了思考其制作过程和动画效果之外,我们更多的时候去思考其中的模式和工程化相关的东西。

在我们的意识中,动画就是设计师与开发者之间的故事。但有没有想过,我们可以换一种模式:

上图我不想用再多的语言来描述,因为这个过程我和我们的团队还在探讨,等哪一天有了答案和大写一起分享。在这里我们提出一个动画工程师的职能,那么真正的动画工程师应该支承担一些什么呢?又需要去思考些什么呢?我们一起来思考吧,我想有一天会有答案的。

总结

经过一段从事动画开发的体验和感受。我和我们的团队一直在探索一种最佳的模式,希望这种模式能帮助我们降低开发成本,提高开发效率,满足更多需求。同时让自己参与到创意当中,或者在这些方面有一些创新。如果你对这方面的话题感兴趣,也可以周末(6月18日)的iWeb峰会上一起探讨。我将和大家一起聊聊《手淘互动动效的探索》。

有关于动画相关更多的探讨,可以点击这里

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.w3cplus.com/animation/progressive-web-animation.htmlSneaker Podcast

返回顶部