简单介绍 CSS 动画

引用 MDN 对 CSS 动画的说明:

  • CSS 动画模块(CSS Animation)可以让你通过使用关键帧对 CSS 属性的值进行动画处理,例如背景位置和变换。
  • 每个关键帧都描述了动画元素在动画序列中的某个特定时间应该如何呈现。
  • 你可以使用动画模块中的属性来控制动画的持续时间、重复次数、延迟启动等方面。

简单讲,CSS 动画是用于实现元素从一个 CSS 样式配置转换到另一个 CSS 样式配置的呈现效果。

CSS 动画的作用

CSS 动画使得您能够实现一些难以置信的效果点缀您的页面或者应用程序。(MDN

CSS 动画语法介绍

动画语法包括两个部分:

  • 描述动画的样式规则。
  • 指定动画开始、结束以及中间点样式的关键帧。

示例代码:

/* 指定动画的样式规则 */
.div {
  width: 200px;
  height: 200px;
  animation: change 3s;
}

/* 指定动画开始、结束点样式的关键帧。 */
@keyframes change {
  0% {
    background-color: #f00;
  }

  100% {
    background-color: #fff;
  }
}

结合上例,对一个元素创建动画,需要在元素的 CSS 选择器上使用animation属性或其子属性来配置动画时长、动画呈现方式及其他动画运行时相关参数,而动画在不同时间点的关键帧的表现样式则需要通过@keyframes来配置。

CSS 动画属性

animation-name

指定由 @keyframes 定义动画名称标识,多个使用逗号隔开。

animation-duration

设置动画一个周期的时长,值必须为正数或 0,单位:秒(s)或毫秒(ms)。
示例:说明:本文示例代码由 vue3 + element-plus 构建

<template>
  <el-card header="animation-duration">
    <div class="ani-box slow" :class="{ running: playState }">
      <div>10s</div>
    </div>
    <div class="ani-box fast" :class="{ running: playState }">
      <div>3s</div>
    </div>
    <el-button type="primary" @click="handleAnimationRunning"
      >播放动画</el-button
    >
  </el-card>
</template>

<style scoped>
  .ani-box {
    width: 80px;
    height: 80px;
    background-color: darkcyan;
    border-radius: 40px;
    animation-name: move;
    animation-fill-mode: forwards;
    animation-play-state: paused;
    text-align: center;
    line-height: 80px;
    color: wheat;
    font-size: 20px;
    margin-bottom: 20px;
  }

  .ani-box.slow {
    animation-duration: 10s;
  }

  .ani-box.fast {
    animation-duration: 3s;
  }

  .ani-box.running {
    animation-play-state: running;
  }

  @keyframes move {
    0% {
      transform: translateX(0px);
    }

    100% {
      transform: translateX(500px);
    }
  }
</style>

<script setup>
  import { ref } from "vue";
  const playState = ref(false);
  const handleAnimationRunning = () => {
    playState.value = true;
  };
</script>

运行结果:
animation-duration

animation-delay

设置延时,指定从应用动画到元素开始执行之前等待的时间,值可以是负值,单位:秒(s)或毫秒(ms)。

  • 默认值为 0s,表示动画应立即开始。
  • 正值表示动画应在指定的时间量过去后开始。
  • 负值会导致动画立即开始,但是从动画循环的某个时间点开始。

示例代码:

<template>
  <el-card header="animation-delay">
    <div class="ani-box default" :class="{ running: playState }">
      <div>0s</div>
    </div>
    <div class="ani-box positive" :class="{ running: playState }">
      <div>5s</div>
    </div>
    <div class="ani-box negative" :class="{ running: playState }">
      <div>-5s</div>
    </div>
    <el-button type="primary" @click="handleAnimationRunning"
      >播放动画</el-button
    >
  </el-card>
</template>
<style scoped>
  .ani-box {
    width: 80px;
    height: 80px;
    background-color: darkcyan;
    border-radius: 40px;
    text-align: center;
    line-height: 80px;
    color: wheat;
    font-size: 20px;
    margin-bottom: 20px;
    animation-name: move;
    animation-fill-mode: forwards;
    animation-play-state: paused;
    animation-duration: 10s;
    animation-timing-function: linear;
  }

  .ani-box.default {
    animation-delay: 0s;
  }

  .ani-box.positive {
    animation-delay: 5s;
  }

  .ani-box.negative {
    animation-delay: -5s;
  }

  .ani-box.running {
    animation-play-state: running;
  }

  @keyframes move {
    0% {
      transform: translateX(0px);
    }

    100% {
      transform: translateX(500px);
    }
  }
</style>
<script setup>
  import { ref } from "vue";
  const playState = ref(false);
  const handleAnimationRunning = () => {
    playState.value = true;
  };
</script>

运行结果:
animation-delay

animation-direction

设置动画是正向播放、反向播放、还是在正向和反向之间交替播放。可选值:

  • normal:动画在每个循环中正向播放。即每次动画循环时,动画将重置为起始状态并重新开始。默认值。
  • reverse:动画在每个循环中反向播放。即每次动画循环时,动画将重置为结束状态并重新开始。
  • alternate:动画在每个循环中正反交替播放,第一次是正向播放。
  • alternate-rever:动画在每个循环中正反交替播放,第一次是反向播放。

示例代码:

<template>
  <el-card header="animation-direction">
    <div class="ani-box normal" :class="{ running: playState }">
      <div>normal</div>
    </div>
    <div class="ani-box reverse" :class="{ running: playState }">
      <div>reverse</div>
    </div>
    <div class="ani-box alternate" :class="{ running: playState }">
      <div>alternate</div>
    </div>
    <div class="ani-box alternate-reverse" :class="{ running: playState }">
      <div>alternate-reverse</div>
    </div>
    <el-button type="primary" @click="handleAnimationRunning"
      >播放动画</el-button
    >
  </el-card>
</template>
<style scoped>
  .ani-box {
    width: 80px;
    height: 80px;
    background-color: darkcyan;
    border-radius: 40px;
    text-align: center;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: wheat;
    font-size: 16px;
    margin-bottom: 20px;
    animation-name: move;
    animation-fill-mode: forwards;
    animation-play-state: paused;
    animation-duration: 2s;
    animation-timing-function: linear;
    animation-iteration-count: 4;
  }

  .ani-box.normal {
    animation-direction: normal;
  }

  .ani-box.reverse {
    animation-direction: reverse;
  }

  .ani-box.alternate {
    animation-direction: alternate;
  }

  .ani-box.alternate-reverse {
    animation-direction: alternate-reverse;
  }

  .ani-box.running {
    animation-play-state: running;
  }

  @keyframes move {
    0% {
      transform: translateX(0px);
    }

    100% {
      transform: translateX(500px);
    }
  }
</style>
<script setup>
  import { ref } from "vue";
  const playState = ref(false);
  const handleAnimationRunning = () => {
    playState.value = true;
  };
</script>

运行结果:
animation-direction

animation-iteration-count

设置动画在停止前应播放的次数。可选值:

  • infinite:无限循环播放动画。
  • 数值:动画重复次数,默认 1,可以是小数,如 0.5 将动画播放一半,负值无效。

示例代码:

<template>
  <el-card header="animation-iteration-count">
    <div class="ani-box half" :class="{ running: playState }">
      <div>0.5次</div>
    </div>
    <div class="ani-box once" :class="{ running: playState }">
      <div>1次</div>
    </div>
    <div class="ani-box twice" :class="{ running: playState }">
      <div>2次</div>
    </div>
    <el-button type="primary" @click="handleAnimationRunning"
      >播放动画</el-button
    >
  </el-card>
</template>
<style scoped>
  .ani-box {
    width: 80px;
    height: 80px;
    background-color: darkcyan;
    border-radius: 40px;
    text-align: center;
    line-height: 80px;
    color: wheat;
    font-size: 20px;
    margin-bottom: 20px;
    animation-name: move;
    animation-fill-mode: forwards;
    animation-play-state: paused;
    animation-duration: 6s;
    animation-timing-function: linear;
  }

  .ani-box.half {
    animation-iteration-count: 0.5;
  }

  .ani-box.once {
    animation-iteration-count: 1;
  }

  .ani-box.twice {
    animation-iteration-count: 2;
  }

  .ani-box.running {
    animation-play-state: running;
  }

  @keyframes move {
    0% {
      transform: translateX(0px);
    }

    100% {
      transform: translateX(500px);
    }
  }
</style>
<script setup>
  import { ref } from "vue";
  const playState = ref(false);
  const handleAnimationRunning = () => {
    playState.value = true;
  };
</script>

运行结果:
animation-iteration-count

animation-play-state

设置动画运行状态,运行或暂停。可选值:

  • running:设置动画为运行状态。
  • paused:设置动画为暂停状态。

示例代码:

<template>
  <el-card header="animation-play-state">
    <div class="ani-box running">51BLOG</div>

    <el-alert
      title="鼠标悬浮时运行动画,离开时暂停动画。"
      :closable="false"
      show-icon
      type="warning"
    ></el-alert>
  </el-card>
</template>
<style lang="scss" scoped>
  .ani-box {
    width: 80px;
    height: 80px;
    background-color: darkcyan;
    border-radius: 4px;
    text-align: center;
    line-height: 80px;
    color: wheat;
    font-size: 16px;
    text-shadow: 1px 2px 1px red;
    margin-bottom: 30px;
    cursor: pointer;
    animation-name: move;
    animation-fill-mode: forwards;
    animation-play-state: paused;
    animation-duration: 5s;
    animation-timing-function: linear;
  }

  .ani-box:hover {
    animation-play-state: running;
  }

  @keyframes move {
    0% {
      transform: rotate(0);
    }

    100% {
      transform: rotate(360deg);
    }
  }
</style>

运行结果:
animation-play-state

animation-timing-function

设置动画在每个周期的持续时间内如何进行。支持三种类型值:

  • 关键字值
    • ease 默认值,表示动画在中间加速,在结束时减速。等同于 cubic-bezier(0.25, 0.1, 0.25, 1.0)
    • ease-in 表示动画一开始较慢,随着动画属性的变化逐渐加快,直至完成。等同于 cubic-bezier(0.42, 0, 1.0, 1.0)
    • ease-out 表示动画一开始较快,随着动画的进行逐渐减速。等同于 cubic_bezier(0, 0, 0.58, 1.0)
    • ease-in-out 表示动画一开始缓慢变化,随后加速变化,最后再次减速变化。等同于 cubic_bezier(0.42, 0, 0.58, 1.0)
    • linear 表示动画以匀速运动。等同于 cubic-bezier(0.0, 0.0, 1.0, 1.0)
    • step-start 等同于 steps(1, jump-start)
    • step-end 等同于 steps(1, jump-end)

      ease、ease-in、ease-out、ease-in-out、linear 称之为非阶跃(non-step)关键字值,代表了固定的四点值的三次贝塞尔曲线

示例代码:

<template>
  <el-card header="animation-timing-function: ease">
    <div class="ani-box ease" :class="{ running: playState }">
      <div>ease</div>
    </div>
    <div class="ani-box ease-in" :class="{ running: playState }">
      <div>ease-in</div>
    </div>
    <div class="ani-box ease-out" :class="{ running: playState }">
      <div>ease-out</div>
    </div>
    <div class="ani-box ease-in-out" :class="{ running: playState }">
      <div>ease-in-out</div>
    </div>
    <div class="ani-box linear" :class="{ running: playState }">
      <div>linear</div>
    </div>
    <div class="ani-box cubic-bezier" :class="{ running: playState }">
      <div>cubic-bezier</div>
    </div>
    <el-button type="primary" @click="handleAnimationRunning"
      >播放动画</el-button
    >
  </el-card>
</template>
<style scoped>
  .ani-box {
    width: 80px;
    height: 80px;
    background-color: darkcyan;
    border-radius: 40px;
    text-align: center;
    line-height: 1;
    display: flex;
    justify-content: center;
    align-items: center;
    color: wheat;
    font-size: 16px;
    margin-bottom: 20px;
    animation-name: move;
    animation-fill-mode: forwards;
    animation-play-state: paused;
    animation-duration: 10s;
    animation-timing-function: linear;
  }

  .ani-box.ease {
    animation-timing-function: ease;
  }

  .ani-box.ease-in {
    animation-timing-function: ease-in;
  }

  .ani-box.ease-out {
    animation-timing-function: ease-out;
  }

  .ani-box.ease-in-out {
    animation-timing-function: ease-in-out;
  }

  .ani-box.linear {
    animation-timing-function: linear;
  }

  .ani-box.cubic-bezier {
    animation-timing-function: cubic-bezier(0.19, 1, 0.86, 0.01);
  }

  .ani-box.running {
    animation-play-state: running;
  }

  @keyframes move {
    0% {
      transform: translateX(0px);
    }

    100% {
      transform: translateX(800px);
    }
  }
</style>
<script setup>
  import { ref } from "vue";
  const playState = ref(false);
  const handleAnimationRunning = () => {
    playState.value = true;
  };
</script>

运行结果:
animation-timing-function-ease

  • 函数值
    • cubic-bezier(0.1, 0.7, 1, 0.1) 开发者自定义的三次贝塞尔曲线,其中 p1 和 p3 的值必须在 0 到 1 的范围内。
  • Step 函数关键字,语法:steps(n, <jumpterm>)
    语法解析:按照 n 个定格在过渡中显示动画迭代,每个定格等长时间显示。
    例如:如果 n 为 5,则有 5 个步骤。
    动画是否在 0%、20%、40%、60%、80%处,或 20%、40%、60%、80%、100%处暂停,或动画在 0%和 100%之间的 5 个定格,又或者在包括 0%和 100%的情况下设置 5 个定格(0%、25%、50%、75%、100%处),取决于 jumpterm 的值:
    • jump-start 表示一个左连续函数,因此第一个跳跃发生在动画开始时。
    • jump-end 表示一个右连续函数,因此第一个跳跃发生在动画结束时。
    • jump-none 两端都没有跳跃。
    • jump-both 在 0% 和 100% 标记处停留,有效地在动画迭代过程中添加一个步骤。
    • start 等同于 jump-start
    • end 等同于 jump-end
      示例代码:
<template>
  <el-card header="animation-timing-function: steps(n, <jumpterm>)">
    <div class="ani-box jump-start" :class="{ running: playState }">
      <div>jump-start</div>
    </div>
    <div class="ani-box jump-end" :class="{ running: playState }">
      <div>jump-end</div>
    </div>
    <div class="ani-box jump-none" :class="{ running: playState }">
      <div>jump-none</div>
    </div>
    <div class="ani-box jump-both" :class="{ running: playState }">
      <div>jump-both</div>
    </div>
    <el-button type="primary" @click="handleAnimationRunning"
      >播放动画</el-button
    >
  </el-card>
</template>
<style scoped>
  .ani-box {
    width: 80px;
    height: 80px;
    background-color: darkcyan;
    border-radius: 40px;
    text-align: center;
    line-height: 1;
    display: flex;
    justify-content: center;
    align-items: center;
    color: wheat;
    font-size: 16px;
    margin-bottom: 20px;
    animation-name: move;
    animation-fill-mode: none;
    animation-play-state: paused;
    animation-duration: 10s;
  }

  .ani-box.jump-start {
    animation-timing-function: steps(5, jump-start);
  }

  .ani-box.jump-end {
    animation-timing-function: steps(5, jump-end);
  }

  .ani-box.jump-none {
    animation-timing-function: steps(5, jump-none);
  }

  .ani-box.jump-both {
    animation-timing-function: steps(5, jump-both);
  }

  .ani-box.running {
    animation-play-state: running;
  }

  @keyframes move {
    0% {
      transform: translateX(0px);
    }

    100% {
      transform: translateX(800px);
    }
  }
</style>
<script setup>
  import { ref } from "vue";
  const playState = ref(false);
  const handleAnimationRunning = () => {
    playState.value = true;
  };
</script>

运行结果:
animation-timing-function-steps

animation-fill-mode

设置动画在执行之前和之后如何将样式应用于其目标。可选值:

  • none 当未执行动画时,动画将不会将任何样式应用于目标,而是以已经赋予该元素的样式来显示。这是默认值。
  • forward 动画执行完之后,目标将保留由执行遇到的最后一个关键帧计算值,最后一个关键帧取决于 animation-direction 和 animation-iteration-count 的值。
  • backwards 动画将在应用于目标时(执行前)立即应用第一个关键帧中定义的值,并在 animation-delay 期间保留此值。第一个关键帧取决于 animation-direction 的值。
  • both 动画将遵循 forwards 和 backwards 的规则,从而在两个方向上扩展动画属性。

示例代码:

<template>
  <el-card header="animation-fill-mode">
    <div class="ani-box none" :class="{ running: playState }">
      <div>none</div>
    </div>
    <div class="ani-box forwards" :class="{ running: playState }">
      <div>forwards</div>
    </div>
    <div class="ani-box backwords" :class="{ running: playState }">
      <div>backwards</div>
    </div>
    <div class="ani-box both" :class="{ running: playState }">
      <div>both</div>
    </div>
    <el-button type="primary" @click="handleAnimationRunning"
      >播放动画</el-button
    >
  </el-card>
</template>
<style scoped>
  .ani-box {
    width: 80px;
    height: 80px;
    background-color: darkcyan;
    border-radius: 40px;
    text-align: center;
    line-height: 80px;
    color: wheat;
    font-size: 16px;
    margin-bottom: 20px;
    animation-name: move;
    animation-play-state: paused;
    animation-duration: 5s;
    animation-timing-function: linear;
    animation-delay: 1s;
  }

  .ani-box.none {
    animation-fill-mode: none;
  }

  .ani-box.forwards {
    animation-fill-mode: forwards;
  }

  .ani-box.backwords {
    animation-fill-mode: backwards;
  }

  .ani-box.both {
    animation-fill-mode: both;
  }

  .ani-box.running {
    animation-play-state: running;
  }

  @keyframes move {
    0% {
      transform: translateX(200px);
    }

    100% {
      transform: translateX(500px);
    }
  }
</style>
<script setup>
  import { ref } from "vue";
  const playState = ref(false);
  const handleAnimationRunning = () => {
    playState.value = true;
  };
</script>

运行结果:
animation-fill-mode

animation

animation 是上面这些属性的一个简写属性形式。后续会单独写篇文章来详细介绍。

更多推荐