CSS动画

发布于 彦子

本文是为帮助您入门和熟悉CSS动画而编写的,使用它们来为您带来基于Web的接口以及为艺术带来生命。虽然W3C的CSS动画规范仍在修订中,但是如今它已经有大量的内容可以供我们使用了。

对我而言,CSS动画最令人激动的事情之一是,我们可以非常轻松地使用我们已经熟悉的工具来把它们添加进我们的项目中。如果您已经精通HTML和CSS,您就不需要学习新的语言或插件来为您的项目添加动态效果了。HTML和CSS已经足够,这是一个非常大的加分!无论你只是添加一点点引人注目的设计细节,还是添加非常多的动画,都没有问题。

CSS的transitions属性、JavaScript和SVG都可以为网页添加动态效果,而且都值得我们去做试验,但是JavaScript和SVG的内容不会在这本书中进行讲解。我们重点讲CSS动画规范。

我整理本文的目的是,让您知道CSS动画的可能性,并为您进行试验以及创建动画提供一个坚实的基础。本文会给你足够的CSS动画入门的内容,足以让你变得更有创造力!

浏览器前缀一览

如果没有对浏览器前缀有足够的了解,您就没办法把CSS动画学好,所以我们花一点时间来看看我在本文是如何处理这个问题的。

在我写这篇文章时,Firefox、Opera和IE都可以无前缀支持CSS动画了~耶!但是其它的浏览器,包括这些浏览器稍旧的版本,仍需要添加浏览器前缀来对CSS动画提供支持。因此,我强烈建议在你的所有项目中,只要是动画属性都添加前缀。虽然,我们只能说这是需要的。

当然,你在进行试验或者只是在本地进行测试,只添加你使用的浏览器的前缀即可。到生产版本的时候再添加其它前缀即可。

为了方便阅读,我会在本文的代码片段中使用无前缀版本来写CSS动画属性。有些代码段中会包含前缀,还有一些示例也可以在CodePen上找到,你可以编辑并测试运行它们。

好了,我们现在就开始学习动画吧!

CSS动画基础

CSS动画看起来非常复杂,但是其核心动画的基本内容是非常简单的。动画名、关键帧,还有一些是确定移动的方向的内容。

我们从创建CSS动画的基本要素开始。任何CSS动画中都有两个主要的部分:

  • 定义动画
  • 将其赋给指定的HTML元素(或元素)。

你可以按照不同的顺序来写,但是我建议先定义动画,然后再应用它——这样更符合我的处理过程。

@keyframes 规则

要定义CSS动画,我们需要先使用@keyframes 规则来声明关键帧。你还需要给动画命名,便于后面引用。

例如,如果你创建了一个在屏幕上移动的小车的动画,你可能需要把动画命名为像drive这样的,然后你的 @keyframes 规则应该是这样写的:

@keyframes drive {

}

什么是关键帧?

简单来说,你的关键帧就是一个描述在整个动画过程中,会发生变化的属性列表(也就是,哪些属性会改变,如何改变以及什么时候改变)。

列表上的每一次运行,都会被认为是动画的一次迭代。任何你想要看到的动画属性的改变都需要被列在你的关键帧中。对于Animatable属性的列表,Mozilla Developer Network有我迄今为止看到的最全面的内容

在传统动画中,关键帧是动画中的关键点。通常,这些关键内容会先由资深漫画家绘制出来,然后初级的漫画家在每一帧之间绘制过渡的动画,让所有的关键帧都能平稳回放。CSS关键帧的工作方式也是类似的:我们使用关键帧在动画中指定在各个点的动画属性值,浏览器会自动填充各个部分的过渡。如果你有使用过After Effects 或 Flash 这样的软件,你应该已经对于关键帧的概念非常熟悉了。

定义关键帧

动画是由关键帧组成的!在@keyframes声明中,我们有两种方法来对它进行定义:关键字fromto;或百分比。

非常简单的动画可能只是把一个对象从一个地方移动到另一个地方。在这种情况下,关键字fromto非常适合来定义关键帧。

正如它们的名字,通过写关键帧来定义动画从哪里开始,然后到哪里结束。如果我们把它应用在我们上边提到的简单的小车动画,我们可以把我们的小车从它当前的位置(坐标为0)移动到一个往右400px的位置,来让它在屏幕上移动。

@keyframes drive {
    from {
        transform: translateX(0);
    }
    to {
        transform: translateX(400px);
    }
}

在很多情况下,你会想要在不止两个状态之间定义动画,这样的话使用百分比会比较合适。

用百分比定义关键帧,从0%关键帧开始,以100%作为结束。0%100%之间的任何数字都可以定义关键帧,所以使用百分比有非常大的灵活性。当然,如果你喜欢的话,你也可以将百分比和fromto混合使用。

如果我们在小车动画中使用百分比来定义关键帧,它是这样写的:

@keyframes drive {
    0% {
        transform: translateX(0);
    }
    100% {
        transform: translateX(400px);
    }
}

正如你看到的,from相当于0%,而to则相当于100%

如果你的关键帧列表中不包括0%或者100%,元素上现有的动画样式将会直接被用在0%100%的的位置。此外,你不必按照严格的升序排列来列出百分比。一个0%的关键帧仍然会被认为是动画的第一个关键帧,即使它不是按照顺序排列的。这有很大的灵活性可以给关键帧分组,以便以后再查看。

将动画赋给HTML元素

一旦创建了关键帧声明块,就需要准备把动画赋给一个HTML元素或其它元素。我们还需要为HTML元素定义一个简短的属性列表,比如img元素,为它应用我们刚才创建的动画。

第一个属性是animation-name,用于告诉我们的图像,我们为它应用了哪组关键帧:

animation-name: drive;

第二个属性是animation-duration。我们的关键帧定义了整个动画的内容,但是我们并没有声明我们想要让它持续多长。可以把它设置为2s

animation-duration: 2s;

animation-duration的默认值是0,这也就是为什么在我们看到动画出现之前,我们想要将它设置成其它值。它可以取秒(s)或微秒(ms)为单位。

只有设置了这两个属性以及我们刚才定义的关键帧,我们才可以看到动画。

我们完整的CSS是这样写的:

.car {
    animation-name: drive;
    animation-duration:1s;
}

@keyframes drive {
    from {
        transform: translate(0);
    }
    to {
        transform: translate(400px);
    }
}

完成了!我们刚才只是完成了创建一个CSS动画需要的最基础的东西:一组定义好的关键帧;一个动画名称用于绑定HTML元素;以及动画的长度声明。

还有一件事……

有两个附加属性是我在所有的动画中都会显式定义的。一次性地完成动画而不再去修改(或是很长一段时间都不再去修改)的情况是非常罕见的。所以,我发现为我自己创建的每个动画都定义animation-timing-functionanimation-iteration-count属性,这非常方便。

animation-timing-function属性

animation-timing-function属性的默认值是ease。但是,我建议你再显式设置一次这个值,因为它对于动画有非常大的影响(我们会在后面详细说一下它)。对于我们简单的小车示例,我把"timing function"值设置为ease-in

animation-time-function: ease-in;

animation-iteration-count属性

animation-iteration-count属性也是很方便的一个属性,即使你使用的是默认值。这个属性决定了动画会重复播放多少次,它的默认值是1

animation-iteration-count: 1;

作了这些补充之后,我们的最终CSS是这样的:

.car {
    animation-name: drive;
    animation-duration: 2s;
    animation-timing-function: ease-in;
    animation-iteration-count: 1;
}

@keyframes drive {
    from {
        transform: translate(0);
    }
    to {
        transform: translate(400px);
    }
}

查看最终效果。作为我们的第一个示例动画,还是不错的。

探究动画属性

我们已经了解了CSS动画最基础的内容。它涵盖了非常多的内容,但是你很快很发现动画有不同的层,当你在完善动画的同时还想节省时间的时候,你就需要有更多帮助你控制动画的东西了。

幸运的是,有很多的属性可以让我们对CSS动画有更深层次的控制,也可以有更多的润色,让动画更丰富。

本节将着眼于animation-delayanimation-fill-modeanimation-direction 这些属性的使用。我们将使用一个稍微复杂一点的动画,滚动的球,作为我们的下一个示例的基础。我已经创建了一个从左到右移动的球的动画,并通过几个关键帧来演示这些属性是如何派上用场的。这是我们的动画效果。

我们最初的CSS样式:

.ball {
    animation-name: ballmove;
    animation-duration: 2s;
    animation-timing-function: ease-in-out;
    animation-iteration-count: 1;
}

@keyframes ballmove {
    0% {
        transform: translateX(100px) rotate(0);
    }
    20% {
        transform: translateX(-10px) rotate(-0.5turn);
    }
    100% {
        transform: translateX(450px) rotate(2turn);
    }
}

animation-delay属性

在我们最初的示例中,动画是在我们加载页面完成后就立即运行的。那如果我们不想要动画怎么快就开始播放呢?这就是animation-delay上场的时候啦。animation-durationanimation-delay都接受以秒(s)和毫秒(ms)为单位的值,现在为动画设置2sanimation-delay

animation-delay: 2s;

带有延迟的动画:

现在我们已经在球动画开始之前有了一个看起来不错的暂停。你可能注意到了,在动画结束的时候,我们的球会回到原来的位置。这不是结束一个动画最理想的方式。当你的对象在屏幕上移动,你可能希望它能停在结束的位置,而不是回到原来的位置。这就是animation-fill-mode可以完成的东西。

animation-fill-mode属性

animation-fill-mode属性可以接受四个值:nonebackwardsforwardsboth。如果你没有声明这个属性的话,默认值为none。在这个示例中,我们让关键帧一路把球移动到容器的右侧。但是在动画结束的时候,小球又回到了它的初始位置。这是因为animation-fill-mode:none的作用。当动画结束的时候,它会返回自己的初始位置。

我在CodePen上创建了一个示例,你可以为其添加或者改变animation-fill-mode属性的值,来看看结果是否有变化。

animation-fill-mode: forwards

但是,如果我们显式地设置animation-fill-modeforwards,在动画结束之后,我们的小球会保持它最后一帧的样式;在这个示例中,100%的关键帧会把它放到右侧。我们给.ball这个类添加一个属性:

animation-fill-mode: forwards;

现在,我们的小球就会保持在我们动画结束的位置,这可能比较符合常理。效果如下。你可以想象成animation-fill-mode: forwards就是在动画播放过程中用于扩展最后一个关键帧的样式的。

animation-fill-mode: backwards

当使用延迟动画时,将动画的fill-mode设置为backwards非常方便。在我们的示例中,动画有一个2s的延迟,然后它先向左再向右移动。如果没有设置animation-fill-mode属性,在动画经过延迟之后,开始播放,小球会突然跳到0%关键帧定义的位置。这虽然不会像动画结束的时候它就突然回到起点这样,但是看起来还是不太好的。

如果我们加上一个animation-fill-mode属性并设置为backwards,小球就会在我们的animation-delay时变成我们0%关键帧定义的样式。你可以想象成它就是把0%关键帧位置的样式扩展到延迟的位置。

animation-fill-mode: backwards;

预览我们的示例来看看结果:

补充一下,如果没有0%(或from)关键帧,你的动画也可以在 animation-delay的过程中先到达相应的位置。浏览器会使用已经应用到你的目标元素上的样式,作为你的动画的开始关键帧的样式,以替换缺省的初始关键帧,因此会在延迟过程中将你的目标元素先放到对应的位置。不过这并不总是可行的,取决于你项目的设置,有可能是其它的情况,不过有得选择总是好的。

animation-fill-mode: both

还有一个可选的值是both,正如它的字面意思,它是forwardsbackwards的结合。动画可以在开始前就已经是第一个关键帧的样式,然后,在动画完成后,保持最后一个关键帧的样式。

回到我们的示例中。这种情况下,我们会使用both,这样我们的小球就会在它开始前就带有我们第一个关键帧定义的样式,在它结束之后会保持最后一个关键帧的样式。

animation-fill-mode: both;

我们最终的CSS是这样写的:

.ball {
    animation-name: ballmove;
    animation-duration: 2s;
    animation-timing-function: ease-in-out;
    animation-iteration-count: 1;
    animation-delay: 1s;
    animation-fill-mode: both;
}

@keyframes ballmove {
    0% {
        transform: translateX(100px) rotate(0);
    }
    20% {
        transform: translateX(-10px) rotate(-0.5turn);
    }
    100% {
        transform: translateX(450px) rotate(2turn);
    }
}

预览示例,查看结果:

animation-direction属性

还有另一个动画属性我想在这探讨一下,animation-direction。目前为止,我们的动画只能forward播放,而且运行效果也非常不错。但是我们还有其它的选择——有一个非常有用的属性!animation-direction,它的值可以是normal(正常), reverse(反转), alternate(交替)和alternate-reverse(交替反转)。它们听起来有点拗口,但是当你看到它们的使用情况时你就会觉得真是so good。我创建了一个示例,你可以添加或更改animation-direction属性的值来看看它们不同的效果。

它的默认值是normal,这个值是通过你列出的关键帧声明直接播放的。这儿有一个截图。

animation-direction

reverse设置表示你的动画是按照你的关键帧序列反向播放的,就像回绕播放一样。把direction设置为reverse,我们的小球就会从右往左跑了。

animation-direction

效果如下:

如果你的动画的iteration-count属性的值大于1,你可以使用alternate值。第一次按照正常的顺序播放,第二次就会反向播放,然后正向,然后反向……方向交替,从正向开始,直到iteration-count跑完。

animation-direction

效果如下:

最后,alternate-reverse是和alternate一样的意思,除了它是从反方向开始的。通过设置alternate-reverse属性,我们的小球和上一个示例一样交替迭代方向,只不过它是从一个reverse的方向开始的,而不是正常的方向。

animation-direction

如果你的观察力比较敏锐,你可能会注意到我们的animation-timing-function属性会随着animation-direction的反向一起反向,这是CSS动画一个很不错的内置效果。

通过这些简单的例子,我相信你可以发现这些属性对于CSS动画创建非常有趣的效果的用处之大。

简写

你可以使用简写来指定你的动画属性。Thanks god!一个animation定义的动画简写属性可能是这样的:

animation: myAnimation 1s ease-in-out 2s 4;

也就是:animation: <animation-name> <animation-duration> <animation-timing-function> <animation-delay> <animation-iteration-count>

你可能注意到了在不同的示例中,简写属性的顺序不一样,虽然他们运行起来都没问题。在这个特殊的简写中,相似术语的顺序(比如持续和延迟的值)会比较重要。W3C注意事项

该顺序在定义每个动画的时候都是非常重要的:第一个值解析为动画持续的时间,第二个值解析为动画延迟的时间。

W3C目前定义的简写顺序是这样的:

<single-animation> = <single-animation-name> || <time> || <single-animation-timing-function> || <time> || <single-animationiteration-count> || <single-animation-direction> || <single-animation-fill-mode> || <single-animation-play-state>

要使用简写在一个元素中定义多个动画,你需要使用逗号来分隔每个动画的属性值。比如在一个元素中定义两个动画需要这样写:

animation: myAnimation 1s ease-in-out 2s 4, myOtherAnimation 4s ease-out 2s;

理解easing

easing是什么?这一节都是关于easing的内容?没错!Easing是我觉得我们网页设计师目前还探讨得不够的内容之一。

“时间是动画的一部分,它为每一次移动赋予了意义。移动可以是通过简单地在两个不同的位置绘制相同的内容,然后在两者之间插入数个其它的图片来完成。但是这样屏幕上的看到的就只是单纯的移动,而不是动画。”——Harold Whitaker and John Halas, Timing for Animation

而Easing有足够的能力去影响动画的交流。某一个对象是重要的,但是它到达相应位置的方式可能更重要得多。事实上,Timing for Animation这本书详细地写了这方面的内容。虽然我们不太可能做到把动画绘制成像迪斯尼那样的,但是了解如果控制我们动画的移动还是很重要的。

这个移动传达的是对象什么样的情绪、分量和其它关键性格以及沟通的因素。这些移动或变化的过渡为我们提供了一个很好的激流的机会,尽管它们可能只会停留不到一秒的时间。

根据定义,Easing是速率被分配到整个动画过程中的方式。在CSS中,我们的easing是用animation-timing-function属性处理的。我们有三种定义timing的方式:关键字;自定义三次贝塞尔曲线;steps。steps在这里是奇数的,因为它们有自己独特的概念,所以它实际上并没有做任何的easing。我们将在最后的一部分内容中简要地探讨一下steps。

easing关键字

首先,我们来仔细地看一下预定义的关键字选项,来对幕后发生的事情有更进一步的了解。我们预定义的easing关键字是:ease (默认); linear; ease-in; ease-outease-in-out

如果我们要使用linear Easing创建一个在两个关键帧之间一帧一帧线性移动的小球,它的运动效果如下:

easing关键字

对象以保持相同的速度在两个关键帧之间移动。速度从整个动画的开始到结束都是不变的。这通常会被视为非常机械的不自然的移动,因为在现实生活中,没有东西会像它这样以恒定的速度移动。

如果我们用ease-in创建相同类型的插图:

easing关键字

该移动在一开始的时候比较慢,然后在接近终点的时候慢慢加快速度。在一般情况下,这种easing类型创造了一种蓄势待发的加速感。对象在移动过程中的速度加快可以暗示其重量,还可以加上其他的外力来同它配合。

使用ease-out给了我们相反的感受。动画在一开始的时候速度比较快,然后随着慢慢接近终点,速度越来越慢:

easing关键字

结合ease-inease-out的概念,我们得到了ease-in-out,这个值会让对象在中点的时候速度上升到最快,在开始和结束的时候速度较慢。从ease值得到的Easing移动是ease-in-out的变体;ease在结束的时候有一个更剧烈的减速,但是你可以看到它们其实看起来是很相似的。个人而言,我更喜欢ease-in-out,因为在大多数情况下运动比较平衡。

贝塞尔曲线

值得庆幸的是,easing的值我们有不止五个关键字可以选择。在我们希望能够有更多的easing选择的时候,三次贝塞尔曲线来拯救我们了!上面的几个关键字也可以被定义为三次贝塞尔曲线。这些关键字有点像常见贝塞尔曲线的快捷方式。当你需要的控制比上边五个关键字提供的更多的时候,你可以为你的timing函数创建三次贝塞尔曲线,这样easing可选择的值就几乎是无限的!

创建曲线时,我们根据时间来计算动画的进展,然后得到这样的一条代表了动画过程中的速率的曲线。

easing关键字

linear easing关键字对应的贝塞尔曲线

我们不需要去纠结它们背后的所有计算,因为我们的目的不在于此,尽管你对贝塞尔曲线的基础充满好奇心,想要研究它的每个数学方面的细节。理解曲线的关键是:曲线越陡峭代表速度越快,曲线越平坦代表速度越慢。下边这条曲线是ease-in-out关键字对应的贝塞尔曲线。中间是最陡峭,所以移动得最快,最后是平缓的,所以速度变慢了。

easing关键字

ease-in-out关键字对应的贝塞尔曲线

对曲线形状的小调整都会影响导致我们动画的细微差异。每条三次贝塞尔曲线都是通过四个在01这个范围之间的值定义的,这四个值用于表达曲线该如何绘制。

cubic-bezier(0.165, 0.840, 0.440, 1.000)

如果是像上边这样写,那它们对我们大多数人是没有意义的,因为根本不明白它们代表的意思。想要让你打破在数学课上使用旧图形计算器的习惯是需要相当一段时间的。幸运的是,我们可以使用一些工具来让这些数字的意义可视化,也更直观,方便我们理解。

创建三次贝塞尔曲线的工具

我最喜欢的三次贝塞尔曲线生成工具是,Matthew Lein 的 Ceaser,提供了各种不同的预设,并允许你拖动点来创建你自己的贝塞尔曲线,还可以预览你创建的easing。当你对生成的东西满意的时候,你就可以复制它动态生成的代码,并把它放在你的CSS中使用。Ceaser还提供了和Penner easing方程(常用于Flash中,现已被移植到JavaScript、CSS等地方使用)等同的CSS。

创建三次贝塞尔曲线的工具

在Ceaser中创建贝塞尔曲线

Ceaser不是唯一可以取得easing信息的地方,Easings.net展示了一些可选的三次贝塞尔曲线的交互版本,以及由此产生的移动都在同一个地方。Lea Verou的cubic-bezier.com也可以让你创建、比较和分享三次贝塞尔函数。动画是一个可视化的东西,所以有这些可视化编辑器和工具来帮助你取得你想要的移动效果是非常好的。这比靠猜数字来想象更有效得多。

现在我们已经对CSS中的easing有了相对深入的了解,你可以通过对你的动画进行微调,做出合适的easing选择,让你得到需要的运动和信息。

如果这里讨论的easing,timing和动画原则激起了你的兴趣,我强烈推荐你阅读一下迪斯尼动画的十二个基础原则,还有Timing for AnimationThe Animator’s Survival Kit这两本书。传统动画工艺有非常丰富的历史,值得我们去学习。

Timing函数并不是万能的

经过前面关于timing函数可以做的东西的这么多的介绍,有一个更重要的点是,CSS要如何使用我们定义的timing函数。对于关键帧动画,timing函数是在关键帧之间应用的。在很多情况下,你只能给每个动画指定一个timing函数:

.someClass { animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); }

在这种情况下,你的三次贝塞尔函数会在动画中的每个关键帧之间应用。这将决定所有属性之间的移动样式,都会按照你在keyframes中定义的函数运动。

但这并不总是理想的,尤其是那些复杂一些的动画。当你要制作的是比较复杂的运动时,在所有的关键帧之间应用相同的easing,几乎是不可能的。一开始的时候它看起来可能会很奇怪,但是我们可以改变timing函数,将它应用于@keyframes声明块中的中期动画。

@keyframes myAnimation {
    0% { 
        opacity: 0.5; 
    }
    50% {
        opacity: 0.3;
        animation-timing-function: ease-in-out;
    }
    100% { 
        opacity: 1; 
    }
}

在上面的代码中,一个ease-in-out的时间函数会被应用在50%100%的关键帧之间,但是之前设置的时间函数将会被默认用于0%50%关键帧之间。

常见的动画任务

选出最常见的动画基本是不可能的,所以我选了几个示例,包括了一些我认为CSS动画可以使用的比较有用而且有趣的内容。在这里我们将详细讲解每一个示例,把你CSS动画方面的知识应用到实际中。我特意选了那些HTML相当简单的例子,这样我们就可以专注于CSS以及那些驱动动画的特定属性。

无限循环的背景动画

CSS制作这种类型的动画是非常棒的,它可以很简单地建立一个无限循环。为了展示它是如何运行的,我们将创建几朵在天空中飘动的动画云朵。在CodePen上有这个示例,你可以跟着代码学习,接着往下看。

看一下初始的CSS,对于上边的云朵,我们使用了共享的类样式,为它们设置了背景图片、宽度和高度。我们单独为每朵云设置了不同的位置值以及z-index的值,来把它们错开:

.cloud {
    width: 248px;
    height: 131px;
    position: absolute;
    background: transparent url(../images/cloud.png) 0 0 no-repeat;
}

.cloud01 {
    top: 100px;
    left: 300px;
    z-index: 100;
}

.cloud02 {
    top: 240px;
    z-index: 200;
}

现在我们就来让这些云动起来吧!首先,我们先在keyframes中定义动画,把它命名为drift。让这些云动起来,飘过天空。因为我们不希望云在漂移的过程中有任何停顿,所以它们看起来好像完全适合用fromto关键帧。所以,我们的keyframes应该这样写:

@keyframes drift {
    from {
        transform: translateX(-255px);
    }
    to {
        transform: translateX(1350px);
    }
}

这里的关键点是,我选择了一个离左边较远的from值(让云从我们看不见的地方开始飘动),一个离右边较远的to值(让云飘到我们看不见的地方结束)。为了更好的性能,我使用了translate,而不是在keyframes中不同定位(尽管在这样一个小例子中,这点异微不足道)。另外,因为我不可能对所有特定的布局都使用translate,所以可以肯定地说,我的动画不会造成任何布局上的问题或者是无意的覆盖布局样式。这个例子比较简单,所以我们不需要考虑这个问题,但是将动画样式和布局样式分开是一个好习惯。

下一步,我们将为这些云指定同一个动画,只是设置的属性略有不同,但是使用的都是相同的keyframes。把动画的定义以及引用分开,可以很方便地重用动画。对于第一朵云,我们给它指定这个drift动画,持续时间是25s,因为云朵都是慢慢飘的~接着把animation-timing-function的值设置为linear,让它在移动的过程中都保持相同的速度。然后定义我们的animation-iteration-count属性,这可以让我们的云朵在天空中一次又一次地飘啊飘啊飘啊~~无限循环。

.cloud01 { animation: drift 25s linear infinite; }

对于第二朵云,我们使用了相同的动画,但是设置相对复杂一点。我们设置了持续时间为35s,这样它可以比我们的第一朵云飘得更慢更稳~另外,我们还把这个动画延迟了10s,把animation-fill-mode设置为backwards。这样我们的云朵就会在那10s的延迟内先到达我们第一个关键帧(from关键帧)的位置。

.cloud02 {animation: drift 35s linear 10s infinite backwards;}

如果我们预览我们最后的代码,两朵云的动画是不一样的,尽管我们使用了相同的keyframes

能够像这样在多个元素上重用动画,是CSS一个非常有用的特性。只要让它们的属性稍有不同,就可以让相同的一组关键帧变得非常万能。

使用steps让雪碧图动起来

前面提到过steps,现在就来讲一下。Steps和我们的另一个animation-timing-function的其它选项表现得非常不一样,它们有它们自己的怪癖和复杂性。它们通常用于结合雪碧图来创建幻灯片,或一帧一帧的动画。如果你想跟着代码一起来看,可以查看下面示例。

构建网站时经常使用雪碧图,来作为一张大图内包括了很多的小图标或是你在网站上使用的其它的图片。对于动画,雪碧图以类似的方式工作。我们把动画的每一帧都收进了一张图片内,把这张图片作为某个div的背景,然后通过移动背景图片来创建动画。steps用于定义在动画过程中你的背景图片有多少次停顿。

Steps把动画的持续时间根据steps的数量分成了相同的若干部分。这每一个steps就像你动画的帧,不同的是你的动画将被分成一系列停顿,而不是持续的运动。

对于这个示例,我们将使用一个由我的朋友——动画师Scott Benson绘制的角色步行循环。他在After Effects中把这个短短的步行循环作成系列png格式的图片导出,然后我把Photoshop中把它们集中到了一张雪碧图上。(我发现自动雪碧图生成器对于像这样的雪碧图并不好用,所以我使用了Photoshop来制作。)

使用steps让雪碧图动起来

在CSS中,我们先给div指定widthheight的值,让它和我们动画的尺寸相匹配,然后设置背景图像为我们创建的雪碧图。

.sprite {
    width: 245px;
    height: 400px;
    display: block;
    background: transparent url(../images/walker.png) 0 0 no-repeat;
    margin: 3em auto;
}

和其它的timing函数一样,steps使用一组关键帧来定义动画。所以,我们会创建一个keyframes定义,把它命名为walker,然后定义两个关键帧。

@keyframes walker {
    0% {
        background-position: 0 0;
    }
    100% {
        background-position: 0 -4000px;
    }
}

我们的雪碧图的总高度是4000px,我们在这里使用了一个负值,让图片上移。在我们的雪碧图上移的时候,我们的图像中下面的关键帧就会被显示出来。通过上面的关键帧,我们把图像从0 0的位置跳到0 -4000px的位置。

我们可以通过删除0%关键帧来简化这个动画。如果我们没有特别定义一个起始关键帧(在这里,指0%关键帧),之前应用到我们元素的样式将会被作为起点使用。当我们在.sprite类中指定背景图像时,我们已经把背景图像的位置为0 0,所以我们不需要在关键帧中重复。我们稍微简化了的关键帧如下,隐含了第一个关键帧:

@keyframes walker {
    100% {
        background-position: 0 -4000px;
    }
}

随着我们的动画定义完成,我们把它指定给我们的.sprite类来获得动画效果。我们将walker动画指定给类名为.spritediv,给它1秒的持续时间。(如果你想要动画播放得快一些或者慢一些,可以调整duration的值。)我们把animation-timing-function设置为steps(10),表示把动画的持续时间分成10份。10也是雪碧图中帧的数量,这样在动画播放的时候,每一帧都可以看到。

最后同样重要的是,把animation-iteration-count设置为infinite。这个雪碧图将会一直循环下去,这样我们可以看到我们的角色一直一直都在步行。我们给.sprite类添加的属性如下:

.sprite { animation: walker 1s steps(10) infinite; }

你可以在这里查看最后的动画效果

使用animation-play-state来启动或停止动画

默认情况下,动画在它们被分配好的时候就立即开始播放,但是我们可以控制它开始播放的时间。根据我们给它们指定的位置的不同,或者选择播放暂停的时间,可以得到非常有趣的结果。可以把hover或类似性质的事件作为触发器,分配动画或改变动画的属性。当然,如果你把JavaScript和CSS动画结合起来使用,你还可以得到更复杂的交互,可以得到的效果也就更多。不过hover状态是可以独立完成CSS各种效果的。比如说,在hover时创建一个比只有过渡更多的悬停动画效果。

结合前面的那个例子,如果你方便在浏览器中查看的话,你可以看看下面的示例:

这个示例是一个带有文本的标签,在我们鼠标悬停时会旋转,可以多留意一些很棒的新东西。我们先来定义能让我们的标签动起来的动画:

@keyframes spin {
    100% {
        transform: rotate(1turn);
    }
}

我们把这个动画赋给我们的标签,这是一个div元素,设置类名为.sticker。另外,我们在第二行中把animation-play-state的值设置为paused

.sticker {
    animation: spin 10s linear infinite;
    animation-play-state: paused;
}

animation-play-state的值有两个,runningpaused。默认值是running,除非你另外设置了。

如果你现在预览文件,你不会看到任何效果。我们给标签设置了旋转,但是又让它暂停了。为了看到我们努力的成果,我们需要把animation-play-state在某个状态的时候设置为running,在这个例子中,hover就OK了:

.sticker:hover { animation-play-state: running; }

这样,在我们的鼠标悬停的时候,标签就会旋转了;在我们鼠标离开的时候,它就停止旋转;然后我们的鼠标再次悬停在上面的时候,它就从上次离开的地方开始接着旋转。我们不是只在两种不同的状态之间直接切换,在每次hover的时候,我们展示的是一大段变化中的一部分。切换animation-play-state的值,比非传统的hover效果多了一些更多有趣的选项。在一些线性动画中,它是一个很方便的让动画停止的方法,然后在你一切准备好的时候开始播放。

我们完成的CSS代码如下:

body {
    padding:4em; 
    background: #fcfcfc;
}

.wrap {
    width:200px; 
    margin:auto; 
    position:relative;
}

.msg {
    color: whitesmoke;
    text-align: center;
    font-family: serif;
    font-size: 3.5em;
    width: 200px;
    position: absolute;
    margin: 55px 0 0 2px;
    pointer-events: none;
}

.sticker {
    width: 200px;
    height: 200px;
    position: absolute;
    background: url(../images/sticker.png) top center no-repeat;
    animation: spin 10s linear infinite;
    animation-play-state: paused;
}

.sticker:hover {
    animation-play-state: running;
}

@keyframes spin { 
    100% {transform: rotate(1turn); } 
}

查看最后完整的示例

如果把JavaScript和CSS动画结合起来使用,就可以做出更强大的交互效果,而且我也希望你能这样做。你肯定会有兴趣去了解在CSS动画开始、进行和结束的时候,JavaScript中都有哪些动画事件可以使用。它们超出了我这本指南的内容,但是Mozilla Developer Connection中有非常精彩的介绍Craig Buckler也会引导你去了解各个不同的浏览器命名的差异(因为,当然,浏览器制造商并不同意把它们统一命名)。

多个动画,一个对象

目前为止,我们讨论的都是为单个元素应用单个动画,但是我们可以为对象添加不止一个动画,只要我们需要。最常见的方法是动画一前一后,这样动画就可以一个轮着一个播放。巧妙地设置animation-delay属性的值可以让我们用纯CSS来完成这个效果。

如果是让两个动画同时作用在相同的元素上,可能比较有技术性。但是CSS不能把两个或者更多keyframes集合组合起来,所以这样设置的结果通常并不是我们想要的。

为了展示如何把多个动画组合起来,我们让一个小奖章从左边滚入,然后在到达指定的位置前进行缩放。示例如下:

首先我们用keyframes创建两个动画。如果这不是在一本书的上下文中,在到达最后的动画之前会有很多次的调整和预览。但是我们必须跳过这些发现乐趣的步骤,直接看这两个关键帧动画:

@keyframes roll-in {
    0% {
        transform: translateX(-200px) rotate(0deg);
    }
    100% {
        transform: translateX(0) rotate(360deg);
    }
}

@keyframes scale-up {
    0% {
        transform: scale(1);
        animation-timing-function: ease-in;
    }
    25% {
        transform: scale(1.15);
        animation-timing-function: ease-out;
    }
    60% {
        transform: scale(0.9);
        animation-timing-function: ease-in;
    }
    100% {
        transform: scale(1);
        animation-timing-function: ease-out;
    }
}

使用第一个keyframes声明来让我们的奖章(一个div元素,指定其类名为.mol)从左边滚入。它只有两个帧,从左侧移动到右侧,并加入一些旋转。很适合使用fromto来定义关键帧,但是我喜欢统一使用一种方法。

第二个动画是让奖章的大小来回弹动,让它在结束的位置有弹性地结束动画。我们甚至可以改变timing函数,来对它的弹动效果进行微调。

我们需要把这两个动画设置为是一个接一个播放的,把第二个动画的延迟时间和第一个动画的持续时间的值设置相同即可:

.mol {
    animation-name: roll-in, scale-up;
    animation-duration: 1s, 0.75s;
    animation-delay: 0s, 1s;
    animation-timing-function: ease-in, linear;
    animation-iteration-count: 1;
    animation-fill-mode: forwards;
}

把动画属性用逗号分隔开,在罗列动画名字的时候,应该按照相同的顺序。在这种情况下,1s0sease-in都是第一个值,与roll-in动画相关,因为它被命名为第一个,然后第二个值是scale-up。在任何情况下,如果我们只指定了一个值,如animation-iteration-count,它就会被用于两个动画。

把第二个动画的延迟设置为1s,和第一个动画的持续时间保持一致,这样就可以在第一个动画结束的时候马上执行。我们可以持续添加更多的动画,然后相应地调整延迟和持续时间,但是在这个例子中,我们只写了两个,这两个回转动画最后的CSS如下:

.mol {
    width: 174px;
    height: 174px;
    background: transparent url('../images/mol_badge.png') top center no-repeat;
    position: absolute;
    left: 400px;
    animation-name: roll-in, scale-up;
    animation-duration: 1s, 0.75s;
    animation-delay: 0s, 1s;
    animation-timing-function: ease-in, linear;
    animation-iteration-count: 1;
    animation-fill-mode: forwards;
}

@keyframes roll-in {
    0% {
        transform: translateX(-200px) rotate(0deg);
    }
    100% {
        transform: translateX(0px) rotate(360deg);
    }
}

@keyframes scale-up {
    0% {
        transform: scale(1);
        animation-timing-function: ease-in;
    }
    25% {
        transform: scale(1.15);
        animation-timing-function: ease-out;
    }
    60% {
        transform: scale(0.9);
        animation-timing-function: ease-in;
    }
    100% { 
        transform: scale(1); 
    }
}

性能及浏览器支持情况

同往常一样,当我们在使用一些大家认为是很新的东西时,需要确保广泛的跨浏览器的良好体验。确定浏览器支持之后,针对缺乏支持的情况做好良好的计划,这对你创建动画来说是一个良好的开始。

CSS动画 vs JavaScript动画:谁更厉害?

在测试的时候,如这篇发表在欧朋开发者blog上的文章,都表明了CSS动画确实渲染得更快,而且比等效的JavaScript占用更少的内存。这是由于渲染CSS动画更多的是由浏览器内部完成的,这提高了很多效率。

CSS动画还受益于硬件加速性能的提高,尤其是Paul Irish的这篇文章中展示的使用transform的demo。

这些demo,以及那些类似的,都展示了CSS在很多情况下,相比JavaScript有更多提高性能的潜力。你的情况可能因你的目标浏览器而有不同,这正是完成动画任务的关键。但总体而言,CSS动画是一个有力的竞争者。我想我们可以期待一下这些潜在的性能优势能随着浏览器的发展而有所提高。

目前的浏览器支持情况

caniuse.com网站提供了关于浏览器支持最详细的信息。它提供了一个方便查看的图表,显示了哪些浏览器版本支持动画,以及哪些前缀是必须添加的。当你对某个CSS属性的当前或过去的浏览器支持感兴趣的话,可以随时查看这个资源。

如果你要创建一个基于安卓受众的东西,你需要关注CSS动画规范在安卓浏览器3.0及以下版本中的支持情况。部分支持比不支持更麻烦,因为我们并不清楚到底哪部分是支持的哪部分是不支持的。Daniel Eden对于比较旧的安卓浏览器提供了一些有用的建议

不管怎样,只要你想要测试某特定的设备或浏览器版本对项目的支持情况的话,最好先进行全面的测试并确保在那个环境中是可以运行的,以确保它能支持,而且有良好的性能。

但是当动画在不同的浏览器中渲染时,仍然有一些比较小的古怪差异,没有办法得到完全的支持。我希望这种bug会随着动画慢慢成熟的支持支持慢慢地较少。但是目前,你可能还是偶尔会碰到这种情况,因为CSS动画还是一块比较新的内容。

现在可以使用CSS动画了吗?

CSS动画有很广泛的支持,现在我们还是希望在现代浏览器的圈子中,每个人都能看到我们做的web。可惜的是CSS动画还没有完全得到目前使用中的所有浏览器的支持,绝对不行。在生产工作中使用CSS动画需要考虑缺乏浏览器支持的情况,因为除非你的受众非常小众,不过你也只能偶尔碰到一两个。所以制作CSS动画大部分的工作量在于,你需要根据你使用CSS动画的具体情况,为它准备好fallback。

用于细节设计的动画

只为非必要的效果和细节设计使用CSS动画的时候,一个什么都不做的方法往往会导致一个可接受的fallback。浏览器会忽略它们无法解析的CSS,所以如果你提前计划,确保你的网站在不能加载动画的时候,看起来不会非常糟糕,那就ok了。

一定要确保添加的动画只是作为额外的非必要的效果或细节,而不是任何影响到布局或重要任务的关键。我发现使用transforms变换和其它不太可能用于的属性,可以帮忙将布局和动画样式分离,所以关注一下那些应用了动画的元素,在没有动画的情况下展示如何。在你酝酿那些你可能并不真正需要的库和fallback之前,测试一下那些do-nothing的方法究竟是干嘛的。

在文章的前面,有一个云朵无限循环的动画示例。如果你有在不支持动画属性的浏览器中查看过该示例,你会看到两朵停在空中的云。我把它们移出了屏幕,因为作为动画的一部分,它们应该是可以在屏幕上飘进飘出的。相比看到空旷的啥都没有的天空,看到两朵静态的云更好一些,在这种情况下,这也是完全可以接受的结果。

基本动画

在处理涉及重要效果或包含重要内容的动画时,没有做任何处理和测试是绝对不行的。在这种情况下,你有两个选择:实现fallback;使用比CSS有更广泛的浏览器支持的东西来创建动画。

为了保持你的理智以及和维持同事之间的友谊,注意避免重复工作(例如,用CSS和JavaScript编写了同样的动画),除非你必须这样做或者是为了得到什么显著的效益。对同一个东西创建两个版本的代码并不是一个好注意,尤其是那些你需要经常维护的项目。在这种项目中,如果你遇到的是需要重要的旧浏览器支持、包含重要内容的动画,使用JavaScript来解决是最好的选择。

比如说,在CSS中编写一个幻灯片过渡的动画,JavaScript的fallback可以适用于更多的情况,尤其是那些不怎么需要改变的内容。但是,对于某个网站上的一个经常更新的功能模块,为每一个特别的动画都写一个CSS和JavaScript版本的动画,这绝对是在浪费大家的时间。

试验性和娱乐型的项目有时候可能从一些fallback中获益,可以有更多的受众。降低满意度是一个挑战,像Modernizr这样的工具可以为你提供浏览器支持的情况,以及你需要做的调整。它可能会为你提供一个JavaScript版本的代码,或是比较旧的版本,或其它可以在你的项目中运行的版本。

要重视fallback,不要告诉用户说他们的浏览器不是我们希望他们使用的,或任何其它能帮助你吸引用户的方法。如果你创建的动画确实是无法以一个合理的方式在用户的浏览器或设备中运行,就尽力为他们提供那些最重要的内容,不要把它们隐藏了就ok。

如果你发现你不能在你的生产作业中使用CSS动画,不要担心。有很多像CodePenJS Bin这样的网站,为你提供一个实践的平台和社区。

结束语

我希望你喜欢这段学习CSS动画的旅程。这只是介绍了CSS动画可以完成什么,以及把运动做成漂亮的设计细节在网站上展示,这仅仅是个开始。把这些实例和资源作为你实践的开端,试着在自己的项目中使用CSS动画吧~~O(∩_∩)O

本文根据@Val Head的《CSS Animations》电子书所整理,如果感兴趣,可以购买此电子书:http://www.fivesimplesteps.com/products/css-animationsNike React Element