使用 CSS 动画

CSS动画使从一种CSS样式配置过渡到另一种CSS样式配置成为可能。动画包含两个组成部分:描述CSS动画的样式和一组关键帧,这些关键帧指示动画样式的起始和结束状态,以及可能的中间路径点。

与传统的脚本驱动的动画技术相比,CSS动画有三个主要优势

  1. 它们易于用于简单的动画;您甚至无需了解JavaScript即可创建它们。
  2. 即使在中等系统负载下,动画也能运行良好。简单的动画在JavaScript中经常表现不佳。渲染引擎可以使用帧跳过和其他技术来保持性能尽可能流畅。
  3. 让浏览器控制动画序列可以让浏览器通过例如减少当前不可见选项卡中运行的动画的更新频率来优化性能和效率。

配置动画

要创建CSS动画序列,请使用animation属性或其子属性设置要动画化的元素的样式。这使您可以配置动画序列的进度时间、持续时间和其他详细信息。这**不会**配置动画的实际外观,动画外观是使用@keyframes@规则完成的,如下面的使用关键帧定义动画序列部分所述。

animation属性的子属性为

animation-composition

指定当多个动画同时影响同一属性时要使用的复合操作。此属性不是animation简写属性的一部分。

animation-delay

指定元素加载与动画序列开始之间的时间延迟,以及动画是否应立即从其开头或动画中途开始。

animation-direction

指定动画的第一次迭代是向前还是向后,以及后续迭代是否在每次运行序列时交替方向或重置到起点并重复。

animation-duration

指定动画完成一个循环所需的时间长度。

animation-fill-mode

指定动画如何在运行前后将其样式应用于其目标。

注意:在动画forwards填充模式的情况下,动画属性的行为就像包含在一组will-change属性值中一样。如果在动画期间创建了一个新的堆叠上下文,则目标元素在动画结束后会保留堆叠上下文。

animation-iteration-count

指定动画应重复的次数。

animation-name

指定描述动画关键帧的@keyframes@规则的名称。

animation-play-state

指定是暂停还是播放动画序列。

animation-timeline

指定用于控制CSS动画进度的的时间轴。

animation-timing-function

通过建立加速度曲线来指定动画如何通过关键帧进行过渡。

使用关键帧定义动画序列

配置完动画的时间后,您需要定义动画的外观。这是通过使用@keyframes@规则建立一个或多个关键帧来完成的。每个关键帧都描述了动画元素在动画序列的给定时间应该如何呈现。

由于动画的时间在配置动画的CSS样式中定义,因此关键帧使用<percentage>来指示它们发生在动画序列中的时间。0%表示动画序列的第一时刻,而100%表示动画的最终状态。由于这两个时间非常重要,因此它们具有特殊的别名:fromto。两者都是可选的。如果未指定from/0%to/100%,则浏览器使用所有属性的计算值开始或结束动画。

您可以选择包含其他关键帧,这些关键帧描述动画开始和结束之间的中间步骤。

使用动画简写

animation 简写非常有用,可以节省空间。例如,本文中我们一直在使用的某些规则

css
p {
  animation-duration: 3s;
  animation-name: slidein;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

…可以使用 animation 简写来替换。

css
p {
  animation: 3s infinite alternate slidein;
}

要了解有关使用 animation 简写指定不同动画属性值的顺序的更多信息,请参阅 animation 参考页面。

设置多个动画属性值

CSS 动画完整属性可以接受多个值,用逗号分隔。当您想在一个规则中应用多个动画并为每个动画设置不同的持续时间、迭代次数等时,可以使用此功能。让我们看一些简单的示例来解释不同的排列。

在第一个示例中,有三个持续时间和三个迭代次数值。因此,每个动画都分配了一个持续时间和迭代次数值,其位置与动画名称相同。fadeInOut 动画分配了 2.5s 的持续时间和 2 的迭代次数,而 bounce 动画分配了 1s 的持续时间和 5 的迭代次数。

css
animation-name: fadeInOut, moveLeft300px, bounce;
animation-duration: 2.5s, 5s, 1s;
animation-iteration-count: 2, 1, 5;

在第二个示例中,设置了三个动画名称,但只有一个持续时间和迭代次数。在这种情况下,所有三个动画都具有相同的持续时间和迭代次数。

css
animation-name: fadeInOut, moveLeft300px, bounce;
animation-duration: 3s;
animation-iteration-count: 1;

在第三个示例中,指定了三个动画,但只有两个持续时间和迭代次数。在这种情况下,如果列表中的值不足以分别分配给每个动画,则值分配将从第一个循环到可用列表中的最后一个项目,然后循环回第一个项目。因此,fadeInOut 获取 2.5s 的持续时间,moveLeft300px 获取 5s 的持续时间,这是持续时间值列表中的最后一个值。现在,持续时间值分配重置为第一个值;因此,bounce 获取 2.5s 的持续时间。迭代次数值(以及您指定的任何其他属性值)将以相同的方式分配。

css
animation-name: fadeInOut, moveLeft300px, bounce;
animation-duration: 2.5s, 5s;
animation-iteration-count: 2, 1;

如果动画和动画属性值的数量不匹配被反转,例如,三个 animation-name 值有五个 animation-duration 值,则在这种情况下,额外的或未使用的动画属性值(两个 animation-duration 值)不应用于任何动画,并将被忽略。

示例

注意:某些旧版浏览器(2017 年之前)可能需要前缀;您可以在浏览器中单击以查看的实时示例包含 -webkit 前缀语法。

使文本在浏览器窗口中滑动

此基本示例使用 translatescale 过渡属性为 <p> 元素设置样式,以便文本从浏览器窗口的右侧边缘滑入。

css
p {
  animation-duration: 3s;
  animation-name: slidein;
}

@keyframes slidein {
  from {
    translate: 150vw 0;
    scale: 200% 1;
  }

  to {
    translate: 0 0;
    scale: 100% 1;
  }
}

在此示例中,<p> 元素的样式指定动画应从开始到结束执行 3 秒,使用 animation-duration 属性,并且定义动画序列关键帧的 @keyframes at-rule 的名称为 slidein

在这种情况下,我们只有两个关键帧。第一个出现在 0%(使用别名 from)。在这里,我们将元素的 translate 属性配置为 150vw(即,超出包含元素的最右侧边缘),并将元素的 scale 设置为 200%(或其默认内联大小的两倍),导致段落宽度是其 <body> 包含块的两倍。这导致动画的第一帧的标题绘制在浏览器窗口的右侧边缘之外。

第二个关键帧出现在 100%(使用别名 to)。translate 属性设置为 0%,元素的 scale 设置为 1,即 100%。这会导致标题在其默认状态下完成动画,与内容区域的左边缘齐平。

html
<p>
  The Caterpillar and Alice looked at each other for some time in silence: at
  last the Caterpillar took the hookah out of its mouth, and addressed her in a
  languid, sleepy voice.
</p>

注意:重新加载页面以查看动画。

添加另一个关键帧动画

让我们在前面示例的动画中添加另一个关键帧。假设我们希望 Alice 的名字变成粉红色并变大,然后在从右到左移动时缩小回其原始大小和颜色。虽然我们可以更改 font-size,但更改任何会对盒模型产生负面影响的属性都会对性能产生负面影响。相反,我们将她的姓名包装在一个 <span> 中,然后分别缩放并为其分配颜色。这需要添加第二个仅影响 <span> 的动画。

css
@keyframes growshrink {
  25%,
  75% {
    scale: 100%;
  }

  50% {
    scale: 200%;
    color: magenta;
  }
}

完整的代码如下所示

css
p {
  animation-duration: 3s;
  animation-name: slidein;
}
p span {
  display: inline-block;
  animation-duration: 3s;
  animation-name: growshrink;
}

@keyframes slidein {
  from {
    translate: 150vw 0;
    scale: 200% 1;
  }

  to {
    translate: 0 0;
    scale: 100% 1;
  }
}

@keyframes growshrink {
  25%,
  75% {
    scale: 100%;
  }

  50% {
    scale: 200%;
    color: magenta;
  }
}

我们在“Alice”周围添加了一个 <span>

html
<p>
  The Caterpillar and <span>Alice</span> looked at each other for some time in
  silence: at last the Caterpillar took the hookah out of its mouth, and
  addressed her in a languid, sleepy voice.
</p>

这告诉浏览器,在动画的前 25% 和后 25% 中,名称应保持正常,但在中间部分变为粉红色并放大和缩小。我们将跨度的 display 属性设置为 inline-block,因为 transform 属性不会影响非替换的 内联级内容

注意:重新加载页面以查看动画。

重复动画

要使动画重复自身,请使用 animation-iteration-count 属性指示要重复动画多少次。在这种情况下,让我们使用 infinite 使动画无限重复。

css
p {
  animation-duration: 3s;
  animation-name: slidein;
  animation-iteration-count: infinite;
}

使动画来回移动

这使得它重复,但每次它开始动画时都跳回起点是非常奇怪的。我们真正想要的是让它在屏幕上来回移动。这可以通过将 animation-direction 设置为 alternate 轻松实现。

css
p {
  animation-duration: 3s;
  animation-name: slidein;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

使用动画事件

您可以通过利用动画事件来获得对动画的更多控制权,以及有关动画的有用信息。这些事件由 AnimationEvent 对象表示,可用于检测动画何时开始、结束和开始新迭代。每个事件都包含其发生的时间以及触发该事件的动画的名称。

我们将修改滑动文本示例以在动画事件发生时输出一些有关每个动画事件的信息,以便我们可以了解它们的工作原理。

我们包含了与前一个示例相同的关键帧动画。此动画将持续 3 秒,称为“slidein”,重复 3 次,并且每次都沿相反方向移动。在 @keyframes 中,沿 x 轴操纵缩放和平移以使元素在屏幕上滑动。

css
.slidein {
  animation-duration: 3s;
  animation-name: slidein;
  animation-iteration-count: 3;
  animation-direction: alternate;
}

添加动画事件监听器

我们将使用 JavaScript 代码来监听所有三种可能的动画事件。此代码配置我们的事件监听器;我们会在文档首次加载时调用它以进行设置。

js
const element = document.getElementById("watchme");
element.addEventListener("animationstart", listener, false);
element.addEventListener("animationend", listener, false);
element.addEventListener("animationiteration", listener, false);

element.className = "slidein";

这是非常标准的代码;您可以在 eventTarget.addEventListener() 的文档中获取有关其工作原理的详细信息。此代码最后要做的是在我们将要设置动画的元素上设置 class 为“slidein”;我们这样做是为了启动动画。

为什么?因为 animationstart 事件在动画开始时立即触发,在我们的例子中,这发生在我们代码运行之前。因此,我们将通过将元素的类设置为稍后设置动画的样式来自己启动动画。

接收事件

事件将传递到 listener() 函数,如下所示。

js
function listener(event) {
  const l = document.createElement("li");
  switch (event.type) {
    case "animationstart":
      l.textContent = `Started: elapsed time is ${event.elapsedTime}`;
      break;
    case "animationend":
      l.textContent = `Ended: elapsed time is ${event.elapsedTime}`;
      break;
    case "animationiteration":
      l.textContent = `New loop started at time ${event.elapsedTime}`;
      break;
  }
  document.getElementById("output").appendChild(l);
}

此代码也非常简单。它查看 event.type 以确定发生了哪种动画事件,然后将相应的注释添加到我们用于记录这些事件的 <ul>(无序列表)中。

最后,输出看起来像这样

  • 已开始:经过时间为 0
  • 新循环在 3.01200008392334 秒开始
  • 新循环在 6.00600004196167 秒开始
  • 已结束:经过时间为 9.234000205993652

请注意,这些时间非常接近,但并不完全等于在配置动画时确定的时间。还要注意,在动画的最后一次迭代之后,不会发送 animationiteration 事件;而是发送 animationend 事件。

为了完整起见,以下显示页面内容的 HTML,包括脚本插入有关接收到的事件信息的列表

html
<h1 id="watchme">Watch me move</h1>
<p>
  This example shows how to use CSS animations to make <code>H1</code>
  elements move across the page.
</p>
<p>
  In addition, we output some text each time an animation event fires, so you
  can see them in action.
</p>
<ul id="output"></ul>

这是实时输出。

注意:重新加载页面以查看动画。

设置显示和内容可见性动画

此示例演示了如何设置 displaycontent-visibility 动画。此行为对于创建入口/出口动画非常有用,例如,您希望使用 display: none 从 DOM 中删除容器,但希望它使用 opacity 平滑淡出,而不是立即消失。

支持的浏览器使用 离散动画类型 的变体为 displaycontent-visibility 设置动画。这通常意味着属性将在动画在两者之间过渡的 50% 处在两个值之间切换。

但是,有一个例外,即当动画从 display: nonecontent-visibility: hidden 到可见值时。在这种情况下,浏览器将在两个值之间切换,以便动画内容在整个动画持续时间内显示。

例如

  • 当将 displaynone 动画到 block(或其他可见的 display 值)时,该值将在动画持续时间的 0% 处切换到 block,以便在整个过程中可见。
  • 当将 displayblock(或其他可见的 display 值)动画到 none 时,该值将在动画持续时间的 100% 处切换到 none,以便在整个过程中可见。

HTML

HTML 包含两个 <p> 元素,它们之间有一个 <div>,我们将对其进行从 display noneblock 的动画。

html
<p>
  Click anywhere on the screen or press any key to toggle the
  <code>&lt;div&gt;</code> between hidden and showing.
</p>

<div>
  This is a <code>&lt;div&gt;</code> element that animates between
  <code>display: none; opacity: 0</code> and
  <code>display: block; opacity: 1</code>. Neat, huh?
</div>

<p>
  This is another paragraph to show that <code>display: none;</code> is being
  applied and removed on the above <code>&lt;div&gt; </code>. If only its
  <code>opacity</code> was being changed, it would always take up the space in
  the DOM.
</p>

CSS

css
html {
  height: 100vh;
}

div {
  font-size: 1.6rem;
  padding: 20px;
  border: 3px solid red;
  border-radius: 20px;
  width: 480px;
  opacity: 0;
  display: none;
}

/* Animation classes */

div.fade-in {
  display: block;
  animation: fade-in 0.7s ease-in forwards;
}

div.fade-out {
  animation: fade-out 0.7s ease-out forwards;
}

/* Animation keyframes */

@keyframes fade-in {
  0% {
    opacity: 0;
    display: none;
  }

  100% {
    opacity: 1;
    display: block;
  }
}

@keyframes fade-out {
  0% {
    opacity: 1;
    display: block;
  }

  100% {
    opacity: 0;
    display: none;
  }
}

请注意关键帧动画中包含 display 属性。

JavaScript

最后,我们包含一些 JavaScript 代码来设置事件监听器以触发动画。具体来说,当我们希望它出现时,我们将 fade-in 类添加到 <div>,当我们希望它消失时,我们将 fade-out 类添加到 <div>

js
const divElem = document.querySelector("div");
const htmlElem = document.querySelector(":root");

htmlElem.addEventListener("click", showHide);
document.addEventListener("keydown", showHide);

function showHide() {
  if (divElem.classList[0] === "fade-in") {
    divElem.classList.remove("fade-in");
    divElem.classList.add("fade-out");
  } else {
    divElem.classList.remove("fade-out");
    divElem.classList.add("fade-in");
  }
}

结果

代码呈现如下

另请参阅