原文链接:https://javascript.info/css-animations,translate with ❤️ by zhangbao.

CSS 动画允许我们在不使用 JavaScript 的情况才创建一些简单动画。

JavaScript 可以用来控制 CSS 动画,只用很少的代码就可以制作出更加丰富的动画。

CSS 过渡

CSS 过渡的概念很简单。我们描述了一个属性以及它的变化是如何应用动画的。那么当属性改变的时候,浏览器就会绘制这个动画。

也就是说:我们只需要改变属性。流畅的过渡交由浏览器帮我们完成。

例如:下面的 CSS 描述了一个 backgorund-color 属性会发生 3 秒过渡的动画。

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

现在如果有一个元素使用了 .animated 这个类,那么当背景色 background-color 发生改变时,就会看到 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>

一共有 4 种属性来描述 CSS 过度效果:

  • transition-property

  • transition-duration

  • transition-timing-function

  • transition-delay

我们稍后就会覆盖讲述,现在使用的 transition 属性是其实是上述具体属性的简写形式,按照这样的顺序:property duration timing-function delay。同时我们也可以设置多个属性的过渡效果。

例如,按钮发生过渡动画的属性数增至两个:color 和 fontx-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。

并不是所有的属性都支持动画的,但是多数是。关键字 all 表示“所有属性都应用过度动画”。

transition-duration

transition-duration 用于指定过度动画运行的时间,这是时间遵循 CSS 时间格式:秒 s 或者是毫秒 ms。

transition-delay

transition-delay 指定动画延迟,也就是动画开始之前的等待时间。例如,如果设置了 transition-delay: 1s,就表示动画开始时间要延迟 1 秒发生。

负值也是支持的,表示选择过渡期间的某个时刻作为过渡动画开始起点。例如,如果 transition-duration 是 2s,delay 是 -1s,那么就是从原本过渡动画的 1 秒那个时刻作为动画起始开始的,也就是从半路上。

下面的例子就是使用了 CSS transition 属性从 0 到 9 转移数字:

  1. <style>
  2. #digit {
  3. width: .5em;
  4. overflow: hidden;
  5. font: 32px monospace;
  6. cursor: pointer;
  7. }
  8. #stripe {
  9. display: inline-block
  10. }
  11. </style>
  12. Click below to animate:
  13. <div id="digit"><div id="stripe">0123456789</div></div>
  14. <script>
  15. stripe.onclick = function() {
  16. stripe.classList.add('animate');
  17. };
  18. </script>

transform 是这样动画的:

  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。

下面例子中,如果你点击了数值,动画就会从当前秒开始:

  1. stripe.onclick = function() {
  2. let sec = new Date().getSeconds() % 10;
  3. // 例如, -3s 的话动画就是从高 3 秒处开始
  4. stripe.style.transitionDelay = '-' + sec + 's';
  5. stripe.classList.add('animate');
  6. };

transition-timing-function

时间函数描述了动画过程如何随时间分布,它是开始很慢然后变快,还是反过来这样。

这是一个第一眼看上去最复杂的属性,但是如果花一点时间去学习的话,还是很简单的。

该属性接受两个值:贝塞尔曲线或帧函数。让我们从曲线开始,因为它使用地更频繁。

贝塞尔曲线

事件函数可以设置为贝塞尔曲线,其中4个控制点满足以下条件:

  • 第一个控制点:(0, 0)。

  • 第二个控制点:(1, 1)。

  • 对于中间点,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。

下面这列小火车点击后,会从左到右匀速前进:

  1. <style>
  2. .train {
  3. position: relative;
  4. cursor: pointer;
  5. width: 177px;
  6. height: 160px;
  7. left: 0;
  8. transition: left 5s cubic-bezier(0, 0, 1, 1);
  9. }
  10. </style>
  11. <div class="train" onclick="this.style.left='450px'">🚆</div>

CSS transition 属性是基于贝塞尔曲线的。

……我们怎样才能让列车减速?

我们可以使用另外一个贝塞尔曲线函数:cubic-bezier(0.0, 0.5, 0.5 ,1.0)。

如图:

CSS 动画 - 图2

我们可以看到,开始时很快:曲线上升,然后越来越慢。

下面将上面 .train 使用的贝塞尔曲线替换一下:

  1. .train {
  2. ...
  3. transition: left 5s cubic-bezier(0, 0.5, 0.5, 1.0);
  4. }

这里提供了几个内置的贝塞尔曲线(关键字):linear,ease,ease-in,ease-out 和 ease-in-out。

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

其他的关键字对应的 cubic-bezier 如下所示:

ease* ease-in ease-out ease-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. ...
  3. transition: left 5s ease-out;
  4. }

但它看起来有点不同。

贝塞尔曲线可以使动画“跳出”其范围。

贝塞尔曲线的控制点的 y 坐标可以是任意值:负数或者一个很大的值,使动画超出正常范围。

接下来我们使用下面的动画代码:

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

left 属性从 100px 运动到 400px。

如果我们点击小火车的话,会看到:

  • 首先,火车先倒退:left 值变成小于 100px 的值。

  • 然后,开始前进,会比 400px 稍微大点。

  • 最后回到 400px 这个位置。

为什么会发生 - 如果我们查看给定贝塞尔曲线的图表,这很明显:

CSS 动画 - 图7

我们将第二个点的 y 坐标移动到 0 之下,第三个点的 y 坐标移动到 0 之上,所以这个曲线超出了“正常的”象限。y 值没有落在正常的 0..1 的范围之内。

我们知道,y 值表示“动画过程的完成度”。y = 0 对应属性的初始值,而 y = 1 对应属性变化的最终值。因此,y < 0 表示的是比 left 初始值还要小的值,而 y > 1 表示比最终值还要大的值。

这肯定是一个“软”变种。 如果我们将y值设置为 -99 和 99,那么列车将跳出范围更多。

但是如何为特定任务制作贝塞尔曲线? 有很多工具。例如,我们可以在 http://cubic-bezier.com/ 网站上制作。

帧函数 Steps

时间函数 steps(number of steps[, start/end]) 允许将动画分割成一帧一帧的。

让我们看一下数字的例子。

这是一个数字列表,没有任何动画,只是作为一个来源:

  1. <style>
  2. #digit {
  3. border: 1px solid red;
  4. width: 1.2em;
  5. }
  6. #stripe {
  7. display: inline-block;
  8. font: 32px monospace;
  9. }
  10. </style>
  11. <div id="digit"><div id="stripe">0123456789</div></div>

我们将使列表中红色“窗口”之外的部分不可见,并使列表向左移动每个步骤,从而使数字以离散方式显示。

将有 9 个步骤,每个数字的步骤移动:

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

steps(9, strart) 的第一个参数是步骤数。转换过程被分隔成了 9 个部分(每个占 10% 的进度),中间的之间间隔也会平均分配在每个步骤中,transition: 9s 表示整个动画费时 5s,每个数字分配 1 秒。

第二个参数是两个关键字之一:start 或 end。

start 关键字表示在动画开始的时候就立即进入第一步骤。

我们可以在动画中观察到:当我们点击数字的时候,立即变成 1(第一步),然后在下一秒的时候开始的时候改变。

这个过程是这样进行的:

  • 0s:-10%(第一个 1 秒钟开始的时候,改变立即发生)。

  • 1s:-20%

  • ……

  • 8s:-80%

  • (最后一秒的开始显示最终值)。

针对 end 值的话,表示改变不是在开始的时候就发生的,而是在每个秒钟的最后。

因此,过程看起来就是这样子的:

  • 0s:0

  • 1s:-10%(第一次改变发生在第一个秒钟的最后)。

  • 2s:-20%

  • ……

  • 9s:-90%

帧函数还提供了两种简写形式:

  • step-start:等同于 steps(1, start)。也就是,也就是说,动画立即开始并采取一步。所以它立即开始并完成,好像没有动画一样。

  • step-end:等同于 steps(1, end)。一步动画在 transition-duration 的结尾处执行。

这些值很少使用,因为这不是真正的动画,而是单步更改。

事件 transitionend

当 CSS 动画运动结束后,会触发 transitionend 事件。

这个事件通常用来在动画结束之后做些事情,我们还可以加入动画。

例如,下面示例中的船开始在那里游泳并在点击时返回,每次越往右越远:

CSS 动画 - 图8

这个动画是通过初始化 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

完成动画的属性名。如果我们同事动画了多个属性的话,这个属性就比较有用了。

(注:如果我们同时动画了多个属性,且持续时间是一样的,那么最终只会触发一次 transitionend 事件,对应的属性名是 transition-property 属性值列表里的最后一个;如果同时动画了多个属性,但每个属性持续时间不一,那么每次每个属性结束动画,都会触发一次 transitionend 事件)。

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 动画允许平滑(或不)动画一个或多个 CSS 属性的更改。

它们适用于大多数动画任务。 我们也可以使用 JavaScript 进行动画制作,下一章将专注于此。

与 JavaScript 动画相比,CSS 动画的局限性:

CSS 动画 JavaScript 动画

- 简单事情简单做


- 快和轻 CPU
|
- JavaScript 动画非常灵活。 他们可以实现任何动画逻辑,如元素的“爆炸”。
- 不只是属性变化。 我们可以在 JavaScript 中创建新元素以用于动画。
|

大多数动画可以使用 CSS 实现,如本章所述。 而 transitionend 事件允许在动画之后运行 JavaScript,因此它与代码完美集成。

但在下一章中,我们将做一些 JavaScript 动画来涵盖更复杂的案例。

(完)