CSS 动画可以在不借助 Javascript 的情况下做出一些简单的动画效果。

你也可以通过 Javascript 控制 CSS 动画,使用少量的代码,就能让动画表现更加出色。

CSS 过渡(transition)[#css-transition]

CSS 过渡的理念非常简单,我们只需要定义某一个属性以及如何动态地表现其变化。当属性变化时,浏览器将会绘制出相应的过渡动画。

也就是说:我们只需要改变某个属性,然后所有流畅的动画都由浏览器生成。

举个例子,以下 CSS 会为 backgroud-color 的变化生成一个 3 秒的过渡动画:

  1. .animated {
  2. transition-property: background-color;
  3. transition-duration: 3s;
  4. }

现在,只要一个元素拥有名为 .animated 的类,那么任何背景颜色的变化都会被渲染为 3 秒钟的动画。

单击以下按钮以演示动画:

  1. <button id="color">Click me</button>
  2. <style>
  3. #color {
  4. transition-property: background-color;
  5. transition-duration: 3s;
  6. }
  7. </style>
  8. <script>
  9. color.onclick = function() {
  10. this.style.backgroundColor = 'red';
  11. };
  12. </script>

CSS 提供了四个属性来描述一个过渡:

  • transition-property
  • transition-duration
  • transition-timing-function
  • transition-delay

之后我们会详细介绍它们,目前我们需要知道,我们可以在 transition 中以 property duration timing-function delay 的顺序一次性定义它们,并且可以同时为多个属性设置过渡动画。

请看以下例子,点击按钮生成 colorfont-size 的过渡动画:

  1. <button id="growing">Click me</button>
  2. <style>
  3. #growing {
  4. transition: font-size 3s, color 2s;
  5. }
  6. </style>
  7. <script>
  8. growing.onclick = function() {
  9. this.style.fontSize = '36px';
  10. this.style.color = 'red';
  11. };
  12. </script>

现在让我们一个一个展开看这些属性。

transition-property

transition-property 中我们可以列举要设置动画的所有属性,如:left、margin-left、height 和 color

不是所有的 CSS 属性都可以使用过渡动画,但是它们中的大多数都是可以的。all 表示应用在所有属性上。

transition-duration

transition-duration 允许我们指定动画持续的时间。时间的格式参照 CSS 时间格式:单位为秒 s 或者毫秒 ms

transition-delay

transition-delay 允许我们设定动画开始前的延迟时间。例如,对于 transition-delay: 1s,动画将会在属性变化发生 1 秒后开始渲染。

你也可以提供一个负值。那么动画将会从整个过渡的中间时刻开始渲染。例如,对于 transition-duration: 2s,同时把 delay 设置为 -1s,那么这个动画将会持续 1 秒钟,并且从正中间开始渲染。

这里演示了数字从 09 的动画,使用了 CSS translate 方法:

结果

script.js

style.css

index.html

  1. stripe.onclick = function() {
  2. stripe.classList.add('animate');
  3. };
  1. #digit {
  2. width: .5em;
  3. overflow: hidden;
  4. font: 32px monospace;
  5. cursor: pointer;
  6. }
  7. #stripe {
  8. display: inline-block
  9. }
  10. #stripe.animate {
  11. transform: translate(-90%);
  12. transition-property: transform;
  13. transition-duration: 9s;
  14. transition-timing-function: linear;
  15. }
  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <link rel="stylesheet" href="style.css">
  6. </head>
  7. <body>
  8. Click below to animate:
  9. <div id="digit"><div id="stripe">0123456789</div></div>
  10. <script src="script.js"></script>
  11. </body>
  12. </html>

如下在 tranform 属性上应用动画:

  1. #stripe.animate {
  2. transform: translate(-90%);
  3. transition-property: transform;
  4. transition-duration: 9s;
  5. }

在以上的例子中,JavaScript 把 .animate 类添加到了元素上,由此触发了动画:

  1. stripe.classList.add('animate');

我们也可以『从中间』开始,也就是说从某个特定数字开始,比方说,从当前的时间的秒数开始。这就要用到负的 transition-delay

此处,如果你单击这个数字,那么它会从当前的秒数开始渲染:

结果

script.js

style.css

index.html

  1. stripe.onclick = function() {
  2. let sec = new Date().getSeconds() % 10;
  3. stripe.style.transitionDelay = '-' + sec + 's';
  4. stripe.classList.add('animate');
  5. };
  1. #digit {
  2. width: .5em;
  3. overflow: hidden;
  4. font: 32px monospace;
  5. cursor: pointer;
  6. }
  7. #stripe {
  8. display: inline-block
  9. }
  10. #stripe.animate {
  11. transform: translate(-90%);
  12. transition-property: transform;
  13. transition-duration: 9s;
  14. transition-timing-function: linear;
  15. }
  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <link rel="stylesheet" href="style.css">
  6. </head>
  7. <body>
  8. Click below to animate:
  9. <div id="digit"><div id="stripe">0123456789</div></div>
  10. <script src="script.js"></script>
  11. </body>
  12. </html>

只需添加一行 JavaScript 代码:

  1. stripe.onclick = function() {
  2. let sec = new Date().getSeconds() % 10;
  3. // for instance, -3s here starts the animation from the 3rd second
  4. stripe.style.transitionDelay = '-' + sec + 's';
  5. stripe.classList.add('animate');
  6. };

transition-timing-function

时间函数描述了动画进程在时间上的分布。它是先慢后快还是先快后慢?

乍一看,这可能是最复杂的属性了,但是稍微花点时间,你就会发现其实也很简单。

这个属性接受两种值:一个贝塞尔曲线(Bezier curve)或者阶跃函数(steps)。我们先从贝塞尔曲线开始,这也是较为常用的。

贝塞尔曲线(Bezier curve)

时间函数可以用贝塞尔曲线描述,通过设置四个满足以下条件的控制点:

  1. 第一个应为:(0,0)
  2. 最后一个应为:(1,1)
  3. 对于中间值,x 必须位于 0..1 之间,y 可以为任意值。

CSS 中设置一贝塞尔曲线的语法为:cubic-bezier(x2, y2, x3, y3)。这里我们只需要设置第二个和第三个值,因为第一个点固定为 (0,0),第四个点固定为 (1,1)

时间函数描述了动画进行的快慢。

  • x 轴表示时间:0 —— 开始时刻,1 —— transition-duration的结束时刻。
  • y 轴表示过程的完成度:0 —— 属性的起始值,1 —— 属性的最终值。

最简单的一种情况就是动画匀速进行,可以通过设置曲线为 cubic-bezier(0, 0, 1, 1) 来实现。

看上去就像这样:

CSS 动画 - 图1

…正如我们所见,这就是条直线。随着时间 x 推移,完成度 y 稳步从 0 增长到 1

例子中的列车匀速地从左侧移动到右侧:

结果

style.css

index.html

  1. .train {
  2. position: relative;
  3. cursor: pointer;
  4. width: 177px;
  5. height: 160px;
  6. left: 0;
  7. transition: left 5s cubic-bezier(0, 0, 1, 1);
  8. }
  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <link rel="stylesheet" href="style.css">
  6. </head>
  7. <body>
  8. <img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='450px'">
  9. </body>
  10. </html>

这个里面的 CSS 就是基于刚才那条曲线的:

  1. .train {
  2. left: 0;
  3. transition: left 5s cubic-bezier(0, 0, 1, 1);
  4. /* JavaScript sets left to 450px */
  5. }

…那么,我们如果表现出减速行驶的列车呢?

我们可以使用另一条贝塞尔曲线:cubic-bezier(0.0, 0.5, 0.5 ,1.0)

图像如下:

CSS 动画 - 图2

正如我们所见,这个过程起初很快:曲线开始迅速升高,然后越来越慢。

这是实际的效果演示:

结果

style.css

index.html

  1. .train {
  2. position: relative;
  3. cursor: pointer;
  4. width: 177px;
  5. height: 160px;
  6. left: 0px;
  7. transition: left 5s cubic-bezier(0.0, 0.5, 0.5, 1.0);
  8. }
  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <link rel="stylesheet" href="style.css">
  6. </head>
  7. <body>
  8. <img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='450px'">
  9. </body>
  10. </html>

CSS:

  1. .train {
  2. left: 0;
  3. transition: left 5s cubic-bezier(0, .5, .5, 1);
  4. /* JavaScript sets left to 450px */
  5. }

CSS 提供几条内置的曲线:lineareaseease-inease-outease-in-out

linear 其实就是 cubic-bezier(0, 0, 1, 1) 的简写 —— 一条直线,刚刚我们已经看过了。

其它的名称是以下贝塞尔曲线的简写:

ease*ease-inease-outease-in-out
(0.25, 0.1, 0.25, 1.0)(0.42, 0, 1.0, 1.0)(0, 0, 0.58, 1.0)(0.42, 0, 0.58, 1.0)
CSS 动画 - 图3
CSS 动画 - 图4
CSS 动画 - 图5
CSS 动画 - 图6

* —— 默认值,如果没有指定时间函数,那么将使用 ease 作为默认值。

所以,我们可以使用 ease-out 来表现减速行驶的列车:

  1. .train {
  2. left: 0;
  3. transition: left 5s ease-out;
  4. /* transition: left 5s cubic-bezier(0, .5, .5, 1); */
  5. }

但是这看起来有点怪怪的。

贝塞尔曲线可以使动画『超出』其原本的范围。

曲线上的控制点的 y 值可以使任意的:不管是负值还是一个很大的值。如此,贝塞尔曲线就会变得很低或者很高,让动画超出其正常的范围。

在一下的例子中使用的代码:

  1. .train {
  2. left: 100px;
  3. transition: left 5s cubic-bezier(.5, -1, .5, 2);
  4. /* JavaScript sets left to 400px */
  5. }

left 本该在 100px400px 之间变化。

但是如果你点击列车,你会发现:

  • 起初,列车会反向运动:left 会变得小于 100px
  • 然后,它会变回往前运动,并且超过 400px
  • 最后再返回 —— 回到 400px

结果

style.css

index.html

  1. .train {
  2. position: relative;
  3. cursor: pointer;
  4. width: 177px;
  5. height: 160px;
  6. left: 100px;
  7. transition: left 5s cubic-bezier(.5, -1, .5, 2);
  8. }
  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <link rel="stylesheet" href="style.css">
  6. </head>
  7. <body>
  8. <img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='400px'">
  9. </body>
  10. </html>

为什么会这样?看一眼给定的贝塞尔曲线的图像你就会明白了。

CSS 动画 - 图7

我们把第二个点的 y 坐标移动到了小于 0 的位置,同时把第三个点的 y 坐标移动到了大于 1 的位置,因此曲线已经不再像一个四分之一圆了。y 坐标超出了常规的 0..1 的范围。

正如我们所知,y 表示『动画进程的完成度』。y = 0 表示属性的初始值,y = 1 则表示属性的最终值。因此,y < 0 意味着属性值要比初始值小,而 y > 1 则表明属性值要比最终值大。

当然了,-12 还是比较缓和的值。如果我们把 y 设为 -9999,那么列车将会偏离地更远。

但是,如何针对特定的任务寻找到合适的贝塞尔曲线呢?事实上,有很多工具可以帮到你。比方说,我们可以利用这个网站:http://cubic-bezier.com/

阶跃函数(Steps)

时间函数 steps(number of steps[, start/end]) 允许你让动画分段进行,number of steps 表示需要拆分为多少段。

让我们通过一个数字的例子来演示一下。我们将会让数字以离散的方式变化,而不是以连续的方式。

为了达到效果,我们把动画拆分为 9 段:

  1. #stripe.animate {
  2. transform: translate(-90%);
  3. transition: transform 9s steps(9, start);
  4. }

step(9, start) 生效时:

结果

style.css

index.html

  1. #digit {
  2. width: .5em;
  3. overflow: hidden;
  4. font: 32px monospace;
  5. cursor: pointer;
  6. }
  7. #stripe {
  8. display: inline-block
  9. }
  10. #stripe.animate {
  11. transform: translate(-90%);
  12. transition-property: transform;
  13. transition-duration: 9s;
  14. transition-timing-function: steps(9, start);
  15. }
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <link rel="stylesheet" href="style.css">
  6. </head>
  7. <body>
  8. Click below to animate:
  9. <div id="digit"><div id="stripe">0123456789</div></div>
  10. <script>
  11. digit.onclick = function() {
  12. stripe.classList.add('animate');
  13. }
  14. </script>
  15. </body>
  16. </html>

steps 的第一个参数表示段数。这个过渡动画将会被拆分为 9 个部分(每个占 10%)。时间间隔也会以同样的方式被拆分:9 秒会被分割为多个时长 1 秒的间隔。

第二个参数可以取 startend 两者其一。

start 表示在动画开始时,我们需要立即开始第一段的动画。

可以观察到,在动画过程中:当我们单击数字之后,它会立马变为 1(即第一段),然后在下一秒开始的时候继续变化。

具体的流程如下:

  • 0s —— -10%(在第一秒开始的时候立即变化)
  • 1s —— -20%
  • 8s-80%
  • (最后一秒,显示最终值)

另一个值 end 表示:改变不应该在最开始的时候发生,而是发生在每一段的最后时刻。

其流程如下:

  • 0s —— 0
  • 1s —— -10%(在第一秒结束时第一次变化)
  • 2s —— -20%
  • 9s —— -90%

step(9, end) 生效时:

结果

style.css

index.html

  1. #digit {
  2. width: .5em;
  3. overflow: hidden;
  4. font: 32px monospace;
  5. cursor: pointer;
  6. }
  7. #stripe {
  8. display: inline-block
  9. }
  10. #stripe.animate {
  11. transform: translate(-90%);
  12. transition-property: transform;
  13. transition-duration: 9s;
  14. transition-timing-function: steps(9, end);
  15. }
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <link rel="stylesheet" href="style.css">
  6. </head>
  7. <body>
  8. Click below to animate:
  9. <div id="digit"><div id="stripe">0123456789</div></div>
  10. <script>
  11. digit.onclick = function() {
  12. stripe.classList.add('animate');
  13. }
  14. </script>
  15. </body>
  16. </html>

另外还有一些简写值:

  • step-start —— 等同于 steps(1, start)。即:动画立刻开始,并且只有一段。也就是说,会立刻开始,紧接着就结束了,宛如没有动画一样。
  • step-end —— 等同于 steps(1, end)。即:在 transition-duration 结束时生成一段动画。

这些值很少会被用到,因为它们并不算是真正的动画,而是单步的变化。

transitionend 事件

CSS 动画完成后,会触发 transitionend 事件。

这被广泛用于在动画结束后执行某种操作。我们也可以用它来串联动画。

举例来说,下面的小船会在点击后向右浮动,然后再回来。而且,每一次都会向右移动地更远一点:

这个动画通过 go 函数初始化,并且在每次动画完成后都会重复执行,并转变方向:

  1. boat.onclick = function() {
  2. //...
  3. let times = 1;
  4. function go() {
  5. if (times % 2) {
  6. // 向右移动
  7. boat.classList.remove('back');
  8. boat.style.marginLeft = 100 * times + 200 + 'px';
  9. } else {
  10. // 向左移动
  11. boat.classList.add('back');
  12. boat.style.marginLeft = 100 * times - 200 + 'px';
  13. }
  14. }
  15. go();
  16. boat.addEventListener('transitionend', function() {
  17. times++;
  18. go();
  19. });
  20. };

transitionend 的事件对象有几个特定的属性:

event.propertyName :当前完成动画的属性,这在我们同时为多个属性加上动画时会很有用。

event.elapsedTime :动画完成的时间(按秒计算),不包括 transition-delay

关键帧动画(Keyframes)

我们可以通过 CSS 提供的 @keyframes 规则整合多个简单的动画。

它会指定某个动画的名称以及相应的规则:哪个属性,何时以及何地渲染动画。然后使用 animation 属性把动画绑定到相应的元素上,并为其添加额外的参数。

这里有个详细的例子:

  1. <div class="progress"></div>
  2. <style>
  3. @keyframes go-left-right { /* 指定一个名字:"go-left-right" */
  4. from { left: 0px; } /* 从 left: 0px 开始 */
  5. to { left: calc(100% - 50px); } /* 移动至 left: 100%-50px */
  6. }
  7. .progress {
  8. animation: go-left-right 3s infinite alternate;
  9. /* 把动画 "go-left-right" 应用到元素上
  10. 持续 3 秒
  11. 持续次数:infinite
  12. 每次都改变方向
  13. */
  14. position: relative;
  15. border: 2px solid green;
  16. width: 50px;
  17. height: 20px;
  18. background: lime;
  19. }
  20. </style>

有许多关于 @keyframes 的文章以及一个详细的规范说明

很可能你并不需要经常用到 @keyframes,除非你的网站上有一直在运动的元素。

总结

CSS 动画允许你为一个或者多个属性的变化创建丝滑流畅(也可能不是)的过渡动画。

它们适用于大多数的动画需求。我们也可以使用 JavaScript 创建动画,下一章将会详细讲解相关内容。

相对于 JavaScript 动画,CSS 动画存在的特点如下:

优点

  • 简单的事,简单地做。
  • 快速,而且对 CPU 造成的压力很小。

不足

  • JavaScript 动画更加灵活。它们可以实现任何动画逻辑,比如某个元素的爆炸效果。
  • 不仅仅只是属性的变化。我们还可以在 JavaScript 中生成新元素用于动画。

本节已经介绍了可以使用 CSS 实现的主要动画类型,而且 transitionend 还允许在动画结束后执行 JavaScript 代码,因此它可以方便得与代码结合起来。

但是在下一节,我们将会学习一些 JavaScript 动画来实现更加复杂的效果。

任务

让飞机动起来(CSS)

重要程度: 5

生成如下图的动画(点击显示):

  • 点击后,图片会从 40x24px 变为 400x240px (变大十倍)。
  • 动画持续三秒。
  • 在动画结束后,输出:“Done!”。
  • 动画过程中,如果飞机被点击,这些操作不应该打断动画。

打开一个任务沙箱。

解决方案

使用 CSS 为 widthheight 属性生成动效:

  1. /* 原始类 */
  2. #flyjet {
  3. transition: all 3s;
  4. }
  5. /* JS 添加的 .growing */
  6. #flyjet.growing {
  7. width: 400px;
  8. height: 240px;
  9. }

请注意,transitionend 会被触发两次 —— 每个属性触发一次。因此,如果我们不进行额外检查的话,这条信息会显示两次。

使用沙箱打开解决方案。

为飞机生成动画(CSS)

重要程度: 5

修改前一个的任务 让飞机动起来(CSS) 的解决方案,让飞机超过原有的大小 400x240px(跳脱出来),然后再回到之前的大小。

这里是效果演示(点击飞机):

在前一个解决方案的基础上做修改。

解决方案

我们需要为此选择合适的贝塞尔曲线。它应该在某个地方拥有 y > 1,来使得飞机『跳出来』。

举例来说,我们可以使两个控制点的 y 都大于 1cubic-bezier(0.25, 1.5, 0.75, 1.5)

如图:

CSS 动画 - 图8

使用沙箱打开解决方案。

圆圈动画

重要程度: 5

创建一个函数:showCircle(cx, cy, radius),来显示一个不断变大的圆。

  • cx,cy 为圆心相对于窗口的位置。
  • radius 为圆的半径。

点击下方的按钮以演示效果:

源文件中提供了一个具有合适样式的圆样例,因此你需要做的就是创建合适的动画。

打开一个任务沙箱。

解决方案

使用沙箱打开解决方案。