CSS - 动画

119 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情

CSS transitions 可以让CSS属性变化成为一个持续一段时间的过程,而不是立即生效的

也就是告诉浏览器除了绘制初始状态效果和结束状态效果,还需要将他们之间的中间状态进行绘制

比如将一个元素从一个位置移动到另外一个位置,默认在修改完CSS属性后会立即生效

但是我们可以通过CSS transition,让这个过程加上一定的动画效果,包括一定的曲线速率变化

过渡动画 - transition

transition CSS 属性是 transition-property,transition-duration,transition-timing-function 和 transition-delay 的一个简写属性

transition属性是给元素添加对应的形变效果,其本身并不会影响标准流中其它元素的排列和布局方式

属性功能默认值备注
transition-property设置那些属性需要执行过渡动画all1. 可以是多个属性 <single-transition-property># 例如 width、left、transform
2. none: 所有属性都不执行动画 --- 可以用来移除动画
3. all: 所有属性都执行动画
transition-duration过渡动画所需的时间0s支持的单位有s和ms
transition-timing-function动画的变化曲线ease各个变化曲线函数及其表现行为可以参考MDN
transition-delay动画执行的延迟时间0s支持的单位有s和ms
# <xxx># --- 表示的是可以设置一个或多个属性值 多个属性值之间使用逗号隔开
# <xxx>+ --- 表示可以设置一个或多个属性值 多个属性值之间使用空格进行隔开
transition: <single-transition>#

# [A | B] --- 表示A和B中二选一
# A || B || C --- 表示可以是A,B,C中的任意几项 对顺序没有要求,个数也没有要求(但一般会至少有一个对应的值)
<single-transition> = [ none | <single-transition-property> ] || <time> || <easing-function> || <time>

# 如果设置了
# 一个<time> --- 这个<time>指代的是transition-duration
# 两个<time> --- 第一个<time>指代的是 transition-duration
#	          --- 第二个<time>指代的是 transition-delay
# 举例:transition: transform ease-in 1s, width ease-in 1s 1s;
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box {
      width: 300px;
      height: 300px;
      background-color: skyblue;
      /*
      	如果transition属性设置在这里
      	就意味着div.box处于hover之前就设置了transition动画
      	此时在div.box从普通状态转变为hover状态的时候
      	或者从hover状态转变为普通状态的时候都会产生渐变动画
      */
      transition: transform ease 1s, width ease-in 1s 1s;
    }

    .box:hover {
      transform: translateX(100px);
      width: 400px;
    }
  </style>
</head>
<body>
  <div class="box"></div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box {
      width: 300px;
      height: 300px;
      background-color: skyblue;
      transition: transform ease 1s, width ease-in 1s 1s;
    }

    .box:hover {
      transform: translateX(100px);
      width: 400px;
      /*
      	如果渐变动画设置在这里
      	就意味着 div.box处于hover状态的时候,立即加上过渡动画
      	而在div.box从hover状态转变为普通状态的时候,立即移除过渡动画
      	所以此时在元素处于hover状态的时候,会有动画效果
      	而从hover转变为普通状态的时候,并不会存在对应的动画效果
      */
      transition: transform ease 1s, width ease-in 1s 1s;
    }
  </style>
</head>
<body>
  <div class="box"></div>
</body>
</html>

注意事项

  1. 不要使用auto作为过渡动画的起始状态或者结束状态
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box {
      width: 300px;
      height: 300px;
      background-color: skyblue;
      position: relative;
      /*
        在这里定义过渡动画的时候,
        需要显示定义left的值为0
        因为left的值默认是auto
        也就是由浏览器自动决定对应的位置
        而使用auto作为动画的起始状态或结束状态的时候
        过渡动画可能会无法正常执行
      */
      left: 0;
      transition: all ease 1s;
    }

    .box:hover {
      left: 100px;
    }
  </style>
</head>
<body>
  <div class="box"></div>
</body>
</html>
  1. 动画的执行效果取决于动画的起始状态和结束状态, 并不单单由transition的值来决定
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box {
      width: 300px;
      height: 300px;
      margin: 100px auto;
      background-color: skyblue;
      /*
        动画的执行效果取决于动画的起始状态和结束状态
        transition只是在起始状态和结束状态之间添加上了过渡效果
        所以transition和动画如何执行没有决定性的联系
        有关的仅仅是那些属性需要执行,执行曲线,执行时间和执行延迟时间
      */
      transition: all ease 1s;
    }

    .box:hover {
      width: 600px;
    }
  </style>
</head>
<body>
  <div class="box"></div>
</body>
</html>

局限性

但是过渡动画存在一定的局限性:

  1. transition只能定义开始状态和结束状态,不能定义中间状态
  2. transition不能重复执行,除非手动重复触发动画,无法自动对动画进行重复触发
  3. transition需要在特定状态下会触发才能执行,比如某个属性被修改了

如果我们希望可以有更多状态的变化或者可以自动重复执行动画的时候,我们就可以使用CSS Animation

序列帧动画 --- animation

使用animation可以定义一个个的动画序列帧,当这些帧以每秒大于等于16帧的速度进行移动的时候,就可以呈现出比较平滑的动画效果,而这种可以定义中间帧的动画,被称之为关键帧动画,或者叫序列帧动画

一般情况下,可以使用transition执行的动画就使用transition来执行,而不要使用animation

因为transition本质上可以看成是一个只有两帧的animation动画

使用步骤

  1. 使用keyframes定义动画序列(每一帧动画如何执行)
  2. 配置动画执行的名称、持续时间、动画曲线、延迟、执行次数、方向等等

@keyframes

可以使用@keyframes来定义多个变化状态,并且使用animation-name来声明匹配:

  • 关键帧使用percentage来指定动画发生的时间点
  • 0%表示动画的第一时刻,100%表示动画的最终时刻
  • from和to是两个关键字,from等价于0%, to等价于100%
/*
	moveAnim是序列帧动画的名称
	 	--- anim是animation的简写
*/
@keyframes moveAnim {
  /* from === 0% */
  from {
    /* 
    	第一帧所设置的元素状态和初始默认状态是一致的
    	所以在本例中,第一帧是可以省略的
    */
    transform: translate(0, 0);
  }

  /*
  	关键帧和关键帧之间使用换行分割
  	不需要使用逗号或分号来进行分割
  */
  33% {
    transform: translate(0, 200px);
  }

  66% {
    transform: translate(200px, 200px);
  }

  /* to === 100% */
  to {
    transform: translate(200px, 0);
  }
}

animation

CSS animation 属性是 animation-name,animation-duration, animation-timing-function,animation-delay,animation- iteration-count,animation-direction,animation-fill-mode 和 animation-play-state 和 animation-play-state 属性的一个简写属性形式

属性说明备注默认值
animation-name需要执行的关键帧动画名称一般是必填的none
animation-duration指定动画的持续时间单位可以是s也可以是ms0s
animation-timing-function动画的执行曲线可选的曲线函数和transition的曲线函数一致ease
animation-delay延迟执行的时间单位可以是s也可以是ms0s
animation-iteration-count动画执行的次数可以是具体的次数
也可以是关键字infinite,表示无限次
1
animation-direction动画执行方向可选值为normal和reversenormal
animation-fill-mode执行完动画后动画停留在那一帧可选值为none,forwards和backwordsnone
animation-play-state控制动画的执行和暂停该属性一般会结合JavaScript来进行使用
可选值为 paused 和 running
running

animation-fill-mode的值的说明

说明
none默认值
只保留初始的css状态
forwards在动画结束后,保留最后一帧的状态
backwards在动画结束后,保留第一帧的状态
在多数情况下其表现形式和none一致
但是在存在animation-delay的时候
如果设置的值是none,那么其在delay的时候,那么就只保留初始css样式
但是如果此时设置的值是backwards,那么其在delay的时候,会一直处于第一帧所设置的css样式
both根据animation-direction自动在forwards和backwards之间进行切换
.box {
  display: inline-block;
  width: 100px;
  height: 100px;
  background-color: skyblue;

  /* 以下是一些序列帧动画动画的属性 */
  /* animation-name: moveAnim; */
  /* animation-duration: 1s; */
  /* animation-timing-function: ease-in-out; */
  /* animation-delay: 1s; */

  /* iteration表示迭代,一般一次循环就是一次迭代 */
  /*
  	如果设置了animation-delay那么其只会在第一次执行动画之前执行对应的延迟时间
  	如果动画循环执行多次,后边轮次的动画在执行之前并不会存在对应的延迟时间
  */
  /* animation-iteration-count: infinite; */

  /* animation-direction: reverse; */
  /* animation-fill-mode: both; */

  /* 序列帧动画的简写方式 */
  /*
  	如果设置了两个<time> --- 其规则和transition中的规则是一致的
  	第一个<time> 对应的是animation-duration
  	第二个<time> 对应的是animation-delay
  */
  animation: moveAnim 1s ease-in-out 1s infinite forwards reverse;
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box {
      display: inline-block;
      width: 100px;
      height: 100px;
      background-color: skyblue;

      animation: moveAnim 1s ease-in-out 1s infinite;
    }

    /*
      在本例中,from和to的状态和css的初始设置的状态是一致的
    	所以from和to所设置的序列帧都是可以省略的
    */
    @keyframes moveAnim {
      /* from {
        transform: translate(0, 0);
      } */

      25% {
        transform: translate(0, 200px);
      }

      50% {
        transform: translate(200px, 200px);
      }

      75% {
        transform: translate(200px, 0);
      }

      /* to {
        transform: translate(0, 0);
      } */
    }
  </style>
</head>
<body>
  <div class="container">
    <div><button id="btn">paused/running</button></div>
    <div class="box"></div>
  </div>

  <script>
    const btn = document.getElementById('btn')
    const box = document.querySelector('.box')

    let isRunning = true

    btn.addEventListener('click', () => {
      box.style.animationPlayState = isRunning ? 'paused' : 'running'
      isRunning = !isRunning
    })
  </script>
</body>
</html>