前端开发者学堂 - fedev.cn

Web Animation 制作指南

发布于 大漠

Web Animation (Web动画)在Web中的运用越来越广泛,但共制作(开发)并不是件易事。这里将主要总结一下有关于Web Animation制作相关的知识,以供给初次接触动画制作的同学有所帮助。

Web动画实现原理

Web动画的实现原理,是利用了人眼的“视觉暂留”现象,在短时间内连续播放数幅静止的画面,使肉眼因视觉残象产生错觉,而误以为画面在“”。

Web动画中有几个主要的概念:

  • :在动画过程中,每一幅静止画面即为一“帧”。
  • 帧率:即每秒钟播放的静止画面的数量,单位是fps(Frame per second)。
  • 帧时长:即每一幅静止画面的停留时间,单位一般是ms(毫秒)。
  • 跳帧(掉帧/丢帧):在帧率固定的动画中,某一帧的时长远高于平均帧时长,导致其后续数帧被挤压而丢失的现象。

Web Animation 实现方法

到目前为止,Web Animation实现方法主要有以下几种(大家觉见的):

  • GIF图片动画: 设计师直接通过制图软件制作而成,嵌入到Web页面中。其优点制作成本低,无需开发人员介入;缺点是文件大,耗性能,无法人机交互。
  • Flash动画: 设计师(或网页制作师)通过Adobe Flash软件将音乐、声效、动画及富有新意的界面融合在一起,以制作出高品质的网页动态效果。其主要运用于PC端的Web页面中。
  • 视频:将需要的一些动画制作成视频文件插入到Web页面中。
  • CSS Animation:通过CSS的相关特性将GIF、Flash、视频动画(创意)转换成代码
  • JavaScript Animation: Web自带的一些制作动画的JavaScript API。

在手淘中使用的动画主要有两大类:

  • 视频型: 纯视频格式,用户不需要互动,打开手淘可以直接播放或选择跳过(例如:2015年双11揭幕动画)
  • Web动画:称之为交互型动画,主要有前端开发人员根据视觉设计师提供的GIF动画、视频或Flash动画效果制作。具有较强的人机交互功能,用户触发某按钮开始播放动画效果或者用户进入手淘后动画就开始播放,而且在播放过程中还可以做一些其他交互功能(例如:2014年双12揭幕动画)

对应的我们前端所要承载的就是Web Animation中的CSS Animation和JavaScript Animation。

CSS Animation

CSS Animation是目前制作Web动画的一处主流方式,也是W3C规范之一

CSS Animation实现原理较为简单,如果你曾经结束过Flash或者动手制作过GIF动画图,那么对CSS Animation能很好的理解。

CSS Animation制作Web动画分为三部分:

  • 声明动画:通过定制关键帧(@keyframes)来声明一个动画,这个关键相当于Flash动画、GIF动画图中的帧,主要用于控制CSS Animation效果
  • 调用动画:在CSS中对应的元素上通过animation属性调用声明好的对应动画,并且指定动画播放的一些特性,比如播放时间、播放函数等
  • 触发动画:最后一个环节是控制动画触发方式,就好比,我们视频做好了,默认有可能是播放的,也有可能是不播放的。在实际中我们要通过一定的触发方式来触发这些被引用的动画。

声明动画:@keyframes

@keyframes是CSS Animation中的最大功臣,可以在@keyframes集合中定义动画的效果,而这些效果其实就是对应的CSS规则集合。比如:

@keyframes anim-name{
    0% {background-position: 0 0;}
    14.3% {background-position: -180px 0;}
    28.6% {background-position: -360px 0;}
    42.9% {background-position: -540px 0;}
    57.2% {background-position: -720px 0;}
    71.5% {background-position: -900px 0;}
    85.8% {background-position: -1080px 0;}
    100% {background-position: 0 0;}
}

anim-name就是通过@keyframes声明的动画名称,其集合中的百分数(比如0%,100%)就是动画的关键帧,关键帧对应的CSS规则就是实现动画的一些样式规则。

而其中较为麻烦的事情是如何确定关键帧的个数,以及怎么配合相应的动作。下面的工具可以帮助大家快速构建出所需的关键帧

除此之外,也可以借用Adobe Edge Animation软件。有关于@keyframes使用的相关细节可以阅读这篇文章

调用动画: animation

animation属性主要用来调用@keyframes已声明好的动画。其主要包括以下几个属性:

animation属性名 说明
animation-name 定义使用的动画名称,需要和@keyframes声明的动画名称一致
animation-duration 用来指定元素播放动画所持续的时间长
animation-timing-function 动画的播放方式
animation-delay 指定元素动画开始播放的时间
animation-iteration-count 指定元素播放动画的循环次数
animation-direction 指定元素动画播放的方向,包括单向循环和双向循环
animation-play-state 用来控制元素动画的播放状态
animation-fill-mode 动画结束之后,关键帧值是否保留在结束状态时的值

其中animation-nameanimation-iteration-countanimation-directionanimation-play-stateanimation-fill-mode相对来说较为简单,在使用的时候根据其属性值的说明对号入座即可。较为复杂的是animation-durationanimation-delay的配合,特别是多个动画一起使用的时候。为了能很好的解决这方面的问题,除了经验、自己审美感之外,还可以借助Chrome的调式工具来进行调试。如下图所示:

Chrome for css animation devtool

在这个调试工具上,可以很好的帮助你控制好每个动画元素的animation-durationanimation-delay时间,而且能让它们配合的更好。

除了这两个属性之外animation-timing-function也相对复杂一些,其提供了一些关键值:

时间函数名称 说明
ease (逐渐变慢)默认值,ease函数等同于贝塞尔曲线cubic-bezier(0.25, 0.1, 0.25, 1.0)
linear (匀速),linear 函数等同于贝塞尔曲线cubic-bezier(0.0, 0.0, 1.0, 1.0)
ease-in (加速),ease-in 函数等同于贝塞尔曲线cubic-bezier(0.42, 0, 1.0, 1.0)
ease-out (减速),ease-out 函数等同于贝塞尔曲线cubic-bezier(0, 0, 0.58, 1.0)
ease-in-out (加速然后减速),ease-in-out 函数等同于贝塞尔曲线cubic-bezier(0.42, 0, 0.58, 1.0)
cubic-bezier 该值允许你去自定义一个时间曲线
steps() 指定一个阶跃函数

cubic-bezier是通过贝赛尔曲线来计算“转换”过程中的属性值。不过这个过程人肉处理也是非常麻烦的事情,在实际生产是可以使用在线工具(cubic-bezier)来帮你处理:

cubic-bezier

steps() 函数指定了一个阶跃函数,第一个参数指定了时间函数中的间隔数量(必须是正整数);第二个参数可选,接受 startend 两个值,指定在每个间隔的起点或是终点发生阶跃变化,默认为 end

假设有一个3s * 2 (animation-iteration-count: 2;animation-duration: 3s;)的动画,我们分别对它应用 steps(3, start)steps(3, end),做出阶跃函数曲线如下:

steps(3,start):

steps

steps()第一个参数将动画分割成三段。当指定跃点为start 时,动画在每个计时周期的起点发生阶跃(即图中空心圆 → 实心圆)。由于第一次阶跃发生在第一个计时周期的起点处(0s),所以我们看到的第一步动画(初态)就为 1/3 的状态,因此在视觉上动画的过程为 1/3 → 2/3 → 1

在JavaScript中就类似于下面这样:

var animateAtStart = function (steps, duration) {  
    var current = 0;
    var interval = duration / steps;
    var timer = function () {
        current++;
        applyStylesAtStep(current);
        if (current < steps) {
            setTimeout(timer, interval);
        }
    };
    timer();
};

steps(3, end):

steps

当指定跃点为end,动画则在每个计时周期的终点发生阶跃(即图中空心圆 → 实心圆)。由于第一次阶跃发生在第一个计时周期结束时(1s),所以我们看到的初态为0% 的状态;而在整个动画周期完成处(3s),虽然发生阶跃跳到了100% 的状态,但同时动画结束,所以100%的状态不可视。因此在视觉上动画的过程为 0 → 1/3 → 2/3(回忆一下数电里的异步清零,当所有输出端都为高电平的时候触发清零,所以全为高电平是暂态)。

在JavaScript中就类似于下面这样:

var animateAtEnd = function (steps, duration) {  
    var current = 0;
    var interval = duration / steps;
    var timer = function () {
        applyStylesAtStep(current);
        current++;
        if (current < steps) {
            setTimeout(timer, interval);
        }
    };
    timer();
};

有了steps()也就有了Sprites动画。比如2015年年货节揭幕动画。

Web Animation API

Web Animation API称之为Web动画API,是一个新的JavaScript API。它致力于集合CSS3动画的性能、JavaScript的灵活、动画库的丰富等各家所长,将尽可能多的动画控制由原生浏览器实现,并添加许多CSS不具备的变量、控制以及或调的选项。

这里提供两个有关于Web Animation API的视频:

Alex Danilo在Google开发者大会介绍了Web动画API(WAAPI)。这是一个高水平的关于API的概述,关于它如何工作以及可以用于何处

Rachel Nabors 2015年在SFHTML5的演讲。除了对Web animation非常多的激情,还给非技术观众做了相当好的讲解。

.animation()方法

WAAPI核心在于提供了Element.animate()方法,它会返回一个AnimationPlayer,其可以帮助我们做一些有趣的动画。animation()接受两个参数,一个是KeyframeEffects数组,一个是AnimationEffectTimingProperties选项。基本上第一个参数会映射到你放到CSS @keyframes中的内容,第二个参数是你将在你的CSS规则中使用animation-*属性(或animation简写,像我前面用的那样)指定的内容。这里有个关键的好处是我们可以使用变量或重用先前定义的KeyframeEffects,而用CSS的话我们就会被限制只能使用我们先前定义的值。

对于每一个KeyframeEffect,我们把CSS中的百分比偏移量offset变成值为01的小数。它是可选的,如果你没有指定任何值,它们就会平均分布(所以如果你有三个,第一个的偏移量为0,第二个的偏移量为.5,第三个则为1)。你还可以指定一个easing属性,这和CSS中的animation-timing-function一样。KeyframeEffect中的其它属性也都是可以添加动画的属性。每个属性的值都应该和你在JavaScript中使用element.style指定的相匹配,即opacity的值应该是一个数字,而transform应该是字符串。

例如:

var player = document.getElementById('toAnimate').animate([], {
    duration: 700, //动画持续时长,毫秒(ms),相当于animation-duration
    easing: 'ease-in-out', //动画播放函数方式,相当于animation-timing-function
    delay: 10, //动画延迟播放时间,毫秒(ms),相当于animation-delay
    iterations: Infinity, //动画播放次数,相当于animation-iteration-count
    direction: 'alternate', //播放元素的方向,相当于animation-direction
    fill: 'forwards' //动画播放完之后,关键帧是否保留在结束状态,相当于animation-fill-mode
});

AnimationPlayer的播放状态及其控制

调用element.animate(),会返回一个AnimationPlayer对象,然后动画开始播放。可以通过检查只读属性playState来查看当前动画的状态,它会返回如下五个字符串之一。通过调用下面的四种方法之一,还可以修改动画的当前状态:

var player = element.animate(/* ... */);
console.log(player.playState); //"running"
 
player.pause(); //"paused"
player.play();  //"running"
player.cancel(); //"idle"... 跳到初始状态
player.finish(); //"finished"...跳到结束状态

除了runningpausedidlefinished这些状态,还有一个pending状态,定义了当一个播放或暂停任务被挂起时的状态。

播放速度

通过读/写playbackRate属性来改变动画播放速度:

var player = element.animate(/* ... */);
console.log(player.playbackRate); //1
  
player.playbackRate = 2; // 两倍速度,可以加速也可以减速

结束回调

使用CSS过渡,在过渡结束时,通常会触发一个事件。同样,AnimationPlayer可以让你在动画完成,或者调用前面讨论的finish()方法时,指定一个onfinish函数。

注意: 根据规范的内容,设置了无限迭代次数的动画是没有结束的,playbackRate的值也不可能为0。规范还要求调用一个已经存在的oncancel回调,以及使用除了这些回调之外的Promise,这应该会比较受欢迎(虽然目前还没有实现)。

时间轴

每个AnimationPlayer都提供了两个读/写的时间相关的属性——currentTimestartTime。我们现在侧重讲解前者。

currentTime返回当前动画的所在的毫秒数。最大值为delay + (duration * iterations),当然,无限迭代的情况则没有最大值。

var player = element.animate([
    {opacity: 1},
    {opacity: 0}
], {
  	duration: 1000,
  	delay: 500,
  	iterations: 3
});
 
player.onfinish = function() {
  	console.log(player.currentTime); // 3500
};

动画的播放速率会影响时间轴进行的速度。如果你设置的播放率为10,你的最大的currentTime保持不变,但是你会比时间轴快10倍。

因为currentTime是读/写属性,可以使用它来跳转到时间轴上的某个点。它还可以让我们同步两个动画。

多个动画

可以给一个元素多次调用animate(),类似CSS中的多动画。例如,

使用的CSS:

#toAnimate {
  	animation: pulse 1s, activate 3000ms, have-fun-with-it 2.5s;
}
@keyframes pulse {
    /* ... */
}
@keyframes activate {
    /* ... */
}
@keyframes have-fun-with-it {
    /* ... */
}

使用WAAPI:

var animated = document.getElementById('toAnimate');
var pulseKeyframes, //定义关键帧变量
    activateKeyframes,
    haveFunKeyframes;
var pulse = animated.animate(pulseKeyframes, 1000); //第二个参数是持续时间的有效简写
var activate = animated.animate(activateKeyframes, 3000);
var haveFunWithIt = animated.animate(haveFunKeyframes, 2500);

使用WAAPI,它可以创建三个AnimationPlayer,每个都可以暂停、播放、结束、取消,也可以通过时间轴或播放速率来进行控制。

KeyframeEffects

KeyframeEffect传入三个参数:要添加动画的元素、关键帧数组、时间函数timing选项。这个新对象基本上还是为单独动画绘制的蓝图。它不用于启动动画,只能定义动画

var elem = document.getElementById('toAnimate');
var timings = {
  	duration: 1000,
  	fill: 'both'
}
var keyframes = [
  	{ opacity: 1 },
  	{ opacity: 0 }
];

var effect = new KeyframeEffect(elem, keyframes, timings);

Web动画方式的优劣对比

动画在过去的五年里发展得很好,因为强大的CSS支持以及JavaScript新增内容的提升。但是每一种实现动画的方法,都有其缺点和优点。

  • CSS动画因为有硬件加速,所以过渡平滑,而且CSS动画的支持内置在浏览器中,但是规则是在CSS中声明的,需要通过JavaScript来实现值的动态变化。
  • requestAnimationFrame有良好的支持,并在动画中允许浏览器优化,但是它会被中断——如果有很多其它的JavaScript在跑的时候。而且,它往往还需要数学计算来获取倒计时。
  • setInterval是很多开发人员进入动画世界的大门,但是它并不精确,而且可能导致结结巴巴的动画效果。
  • jQuery.animate()也让一些其它的开发者进入了动画大世界,但是经常会有性能问题。
  • 库的话如VelocityGreenSock (GSAP)完善了JavaScript性能,而且经过测试,在很多情况下是最好的选择。但是,它们还是需要维护和加载外部库。

理想情况下,我们可以在浏览器级别打包尽可能多的动画控件放进去。这些库可以专注于提供新特性,还会自动更新。而WAAPI就是在试图做到这一点。它的目标是既有CSS的性能优势,又有JavaScript的优点和灵活性(还有SVG动画),然后把它赋给浏览器,使其工作得更好。

Web动画的原则

迪士尼经过基础工作练习的长时间累积,在 1981 年出版的 The Illusion of Life: Disney Animation 一书中发表了动画的十二个原则 (12 Principles of Animation) 。这些原则描述了动画能怎样用于让观众相信自己沉浸在现实世界中。

了解这些原则,有助于你更好的完成Web动画效果。

挤压和拉伸 (Squash and stretch)

挤压和拉伸

这是物体存在质量且运动时质量保持不变的概念。当一个球在弹跳时,碰击到地面会变扁,恢复的时间会越来越短。创建对象的时候最有用的方法是参照实物,比如人、时钟和弹性球。

当它和网页元件一起工作时可能会忽略这个原则。DOM 对象不一定和实物相关,它会按需要在屏幕上缩放。例如,一个按钮会变大并变成一个信息框,或者错误信息会出现和消失。

预备动作 (Anticipation)

预备动作

运动不倾向于突然发生。在现实生活中,无论是一个球在掉到桌子前就开始滚动,或是一个人屈膝准备起跳,运动通常有着某种事先的累积。

我们能用它去让我们的过渡动画显得更逼真。预备动作可以是一个细微的反弹,帮人们理解什么对象将在屏幕中发生变化并留下痕迹。

例如,悬停在一个元件上时可以在它变大前稍微缩小,在初始列表中添加额外的条目来介绍其它条目的移除方法。

演出布局 (Staging)

演出布局

演出布局是确保对象在场景中得以聚焦,让场景中的其它对象和视觉在主动画发生的地方让位。这意味着要么把主动画放到突出的位置,要么模糊其它元件来让用户专注于看他们需要看的东西。

在网页方面,一种方法是用 model 覆盖在某些内容上。在现有页面添加一个遮罩并把那些主要关注的内容前置展示。

另一种方法是用动作。当很多对象在运动,你很难知道哪些值得关注。如果其它所有的动作停止,只留一个在运动,即使动得很微弱,这都可以让对象更容易被察觉。

还有一种方法是做一个晃动和闪烁的按钮来简单地建议用户比如他们可能要保存文档。屏幕保持静态,所以再细微的动作也会突显出来。

连续运动和姿态对应 (Straight-Ahead Action and Pose-to-Pose)

连续运动和姿态对应

连续运动是绘制动画的每一帧,姿态对应是通常由一个 assistant 在定义一系列关键帧后填充间隔。

大多数网页动画用的是姿态对应:关键帧之间的过渡可以通过浏览器在每个关键帧之间的插入尽可能多的帧使动画流畅。

有一个例外是定时功能step。通过这个功能,浏览器 "steps" 可以把尽可能多的无序帧串清晰。你可以用这种方式绘制一系列图片并让浏览器按顺序显示出来,这开创了一种逐帧动画的风格。

跟随和重叠动作 (Follow Through and Overlapping Action)

跟随和重叠动作

事情并不总在同一时间发生。当一辆车从急刹到停下,车子会向前倾、有烟从轮胎冒出来、车里的司机继续向前冲。

这些细节是跟随和重叠动作的例子。它们在网页中能被用作帮助强调什么东西被停止,并不会被遗忘。例如一个条目可能在滑动时稍滑微远了些,但它自己会纠正到正确位置。

要创造一个重叠动作的感觉,我们可以让元件以稍微不同的速度移动到每处。这是一种在 iOS 系统的视窗 (View) 过渡中被运用得很好的方法。一些按钮和元件以不同速率运动,整体效果会比全部东西以相同速率运动要更逼真,并留出时间让访客去适当理解变化。

在网页方面,这可能意味着让过渡或动画的效果以不同速度来运行。

缓入缓出 (Slow In and Slow Out)

缓入缓出

对象很少从静止状态一下子加速到最大速度,它们往往是逐步加速并在停止前变慢。没有加速和减速,动画感觉就像机器人。

在 CSS 方面,缓入缓出很容易被理解,在一个动画过程中计时功能是一种描述变化速率的方式。

使用计时功能,动画可以由慢加速 (ease-in)、由快减速 (ease-out),或者用贝塞尔曲线做出更复杂的效果。

弧线运动 (Arc)

弧线运动

虽然对象是更逼真了,当它们遵循缓入缓出的时候它们很少沿直线运动——它们倾向于沿弧线运动。

我们有几种 CSS 的方式来实现弧线运动。一种是结合多个动画,比如在弹力球动画里,可以让球上下移动的同时让它右移,这时候球的显示效果就是沿弧线运动。

另外一种是旋转元件,我们可以设置一个在对象之外的原点来作为它的旋转中心。当我们旋转这个对象,它看上去就是沿着弧线运动。

次要动作 (Secondary Action)

"次要动作"

虽然主动画正在发生,次要动作可以增强它的效果。这就好比某人在走路的时候摆动手臂和倾斜脑袋,或者弹性球弹起的时候扬起一些灰尘。

在网页方面,当主要焦点出现的时候就可以开始执行次要动作,比如拖拽一个条目到列表中间。

时间节奏 (Timing)

时间节奏

动画的时间节奏是需要多久去完成,它可以被用来让看起来很重的对象做很重的动画,或者用在添加字符的动画中。

这在网页上可能只要简单调整 animation-durationtransition-duration 值。

这很容易让动画消耗更多时间,但调整时间节奏可以帮动画的内容和交互方式变得更出众。

夸张手法 (Exaggeration)

夸张手法

夸张手法在漫画中是最常用来为某些动作刻画吸引力和增加戏剧性的,比如一只狼试图把自己的喉咙张得更开地去咬东西可能会表现出更恐怖或者幽默的效果。

在网页中,对象可以通过上下滑动去强调和刻画吸引力,比如在填充表单的时候生动部分会比收缩和变淡的部分更突出。

扎实的描绘 (Solid drawing)

扎实的描绘

当动画对象在三维中应该加倍注意确保它们遵循透视原则。因为人们习惯了生活在三维世界里,如果对象表现得与实际不符,会让它看起来很糟糕。

如今浏览器对三维变换的支持已经不错,这意味着我们可以在场景里旋转和放置三维对象,浏览器能自动控制它们的转换。

吸引力 (Appeal)

吸引力

吸引力是艺术作品的特质,让我们与艺术家的想法连接起来。就像一个演员身上的魅力,是注重细节和动作相结合而打造吸引性的结果。

精心制作网页上的动画可以打造出吸引力,例如 Stripe 这样的公司用了大量的动画去增加它们结账流程的可靠性。

扩展阅读

Web动画性能

随着网页功能变得愈发复杂和精细,以及手机端H5发展中所遇到的硬件性能瓶颈,网页的运行时性能问题变得越来越突出。而用户对于网页运行时性能最直观的感受,莫过于UI操作的流畅程度。流畅或卡顿,爽或不爽,皆在于每个UI动画细节之间。

其中帧率能反映动画的流畅程度:

  • 在网页中,帧率能够达到50~60fps的动画将会相当流畅,让人倍感舒适
  • 帧率在30~50fps之间的动画,因各人敏感程度不同,舒适度因人而异
  • 帧率在30fps以下的动画,让人感觉到明显的卡顿和不适感
  • 帧率波动很大的动画,亦会使人感觉到卡顿

帧率能够量化动画的流畅程度,流畅的动画一般具备两个特点:

  • 帧率高(接近60fps最佳)
  • 帧率稳定,波动少(极少出现跳帧现象)

动画调优的策略与技巧

动画调优是有一定的策略与技巧的,下面提供一些参考意见:

提升每一帧性能(缩短帧时长,提高帧率)

  • 避免频繁的重排。
  • 避免大面积的重绘。
  • 优化JS运行性能。

保证帧率平稳(避免跳帧)

  • 不在连续的动画过程中做高耗时的操作(如大面积重绘、重排、复杂JS执行),避免发生跳帧。
  • 若高耗时操作无法避免,则尝试化解,比如:将高耗时操作放在动画开始或结尾处。将高耗时操作分摊至动画的每一帧中处理。

针对硬件加速渲染通道的优化

  • 通过层的变化效果(如transform)实现位移、缩放等动画,可避免重绘。
  • 合理划分层,动静分离,可避免大面积重绘。
  • 使用分层优化动画时,需要留意内存消耗情况(通过Safari调试工具)。

低性能设备优先调试

Android设备优先调试:移动设备的硬件配置一般低于桌面设备,而移动端设备中,Android设备相比于iOS设备性能普遍较差,因此在Andorid设备下性能问题更加明显,幸运的是Android可以借助Chrome自带的远程调试工具方便调试动画性能(Android 4.0+),所以优先调试Android设备可以更早地发现问题,并能更方便地解决问题。

帧率的测试

Chrome自带的帧率监测工具,用于侦听全局帧率,以及页面重绘耗时

帧率的测试

Chrome Timeline,杀手级监测 & 调试工具

Timeline

上面是Chrome浏览器下的监测与调试工具,如果你不太喜欢使用Chrome浏览器,可以使用JavaScript来做这方面的测试,比如Stats.js,它侦听全局或指定位置的帧率。

Web Animation资源

@awwwards-team整理了一份Web Animation Infographics。图中提供了有关于Web Animation各种资源:

在制作Web Animation时,有很多现成的资源可供参考,或者使用,推荐几个链接:

更多的资源集,可以点击这里查阅Nike Ambassador VIII 8