CSS动画知识总结

716 阅读8分钟

动画是如何形成的

比较较真的来说,其实不存在真正意义上的动画,我们看到的动画只是很多静止的画面。之所以我们会觉的画面是动态的,是因为人脑的一个bug:当我们在看很多静止画面不断播放时,肉眼会因为视觉残像,让大脑以为在看动画。

所以所谓动画,其实就是许许多多静止的画面,以一定的速度(比如每秒30张)连续播放形成的。静止的画面叫做帧,而播放速度就是帧率了。一般看电影只需要每秒24帧就能看起来比较流畅了,而游戏至少要每秒30帧以上。

CSS动画的两种做法

假设要完成这样一个简单的需求:让盒子从坐移动到右边,用两种方法分别应该怎么做?

用transition实现

实现思路:使用transform属性里的translateX来完成移动,然后加上transition的过渡效果。

HTML代码:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>demo</title>
</head>
<body>
  <div id="demo"></div>
  <br>
  <button id="start">开始</button>
  <!--写一个div,一个按钮,中间加上分隔线使得看起来好看一点-->
</body>
</html>

CSS代码:

#demo {
  width: 50px;
  height: 50px;
  background-color: red;
  /*给demo加一个简单的样式以方便查看*/
}
#demo.go{
  transform:translateX(100px);
  /*这里提前写一个类go,用translateX来完成向右移动*/
}

JS代码:

start.onclick = function(){
  demo.classList.add('go');
 //给按钮添加点击事件,当点击按钮的时候,就给demo加上go类
}

这时就完成一个移动的效果了:

移动的确实现了,但是效果特别的生硬,这是因为我们还没加transition,所以需要回过头到CSS代码里,给id为demo的div元素加上transition过渡效果:

#demo {
  width: 50px;
  height: 50px;
  background-color: red;
  transition:all 2s linear;
  /*2s是动画完成的时间,而linear则是表示整个动画过程是匀速的*/
}

这时再看就有动画效果了:

所以transition的作用就是,你告诉我开头什么样,结尾什么样,中间的过渡效果我来给你补上去。

用animation实现

实际制作动画的情况中,transition用的往往不多,用的更多的是animation。

前面的HTML代码和CSS代码和transition里的一样:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>demo</title>
</head>
<body>
  <div id="demo"></div>
  <br>
  <button id="start">开始</button>
</body>
</html>
#demo {
  width: 50px;
  height: 50px;
  background-color: red;
}

要使用animation,首先要声明关键帧,要提前想好动画思路:先是在原来的位置不动,然后移动到右边,有两种写法:

1.
@keyframes go {
  0% {transform:none;}
  100% {transform:translateX(100px);}
}

2.
@keyframes go {
  from {transform:none;}
  to {transform:translateX(100px);}
}

/*go是动画名,可以自己取名*/

然后同样是给demo多写一个类:

#demo.begin{
  animation:go 2s;
}

JS代码:

start.onclick = function(){
  demo.classList.add('begin');
}

这时同样也达到了预期的效果:

关于transition的总结

transition就是过渡的意思,作用就是你告诉我一个行为开头什么样,结尾什么样,然后中间我来帮你添加一个过渡的效果。

transition语法
transition:属性名 时长 过渡方式 延迟;

假设做一个3秒后,让div宽度匀速变长的动画,整个动画再5秒内完成:

transitionwidth 5s linear 3s
并非所有属性都能添加过渡效果

注意:并非所有的属性都可以添加transition过渡效果。

比如把 display:block 变成 display:none 就无法添加过渡效果,或者说添加了也没用。那么如果我们想实现一个慢慢消失的效果怎么做呢?

两种方法: 一种是由 opacity:1 变成 opacity:0;

另一种是由 visibility:visible 变成 visibility:hidden;

要注意到这两种方法与display不同的是,使用display:none后盒子消失,并且原来占的位置也会跟着消失,而用opacity和visibility来实现的话,只是视觉上看不到了,但是还是占着原来的位置的,所以这两种方法后面往往会跟这样一个搭配来使得不占位置:

test.addEventListener(ontransitionend,function(){
    test.remove();
})

或者

setTimeout(function(){
    test.remove();
},动画完成的时间)
更复杂的动画,添加中间效果

有时我们需要的效果可能并不是单一的,那么如果想添加一个中间效果,怎么实现呢?

假设我们需要做一个div先向左移动100像素,然后再向下移动100像素的动画:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
  <style>
    #test {
      width: 50px;
      height: 50px;
      background-color: red;
      transition: all 1s linear;
    }
    #test.left {
      transform: translateX(100px);
    }
    #test.down {
      transform: translateX(100px) translateY(100px);
    }
  </style>
</head>
<body>
  <div id="test"></div>
  <button id="start">开始</button>
 <script>
    start.onclick = function(){
      test.classList.add('left');
      setTimeout(function(){
        test.classList.add('down');
      },1000);
    }
 </script>
</body>
</html>

这种方法的主要思路就是事先想好div的三种状态,初始状态、中间状态和最终状态。初始状态过渡到中间状态,再从中间状态过渡到最终状态,中间使用setTimeout()来完成,注意最终状态的代码里要包装中间状态的代码,不然中间状态完成后,div会回到开始的位置。

关于animation的总结

animation是动画的意思,要在网页里实现一个动画效果,相比于transition来说,animation往往是用的更多的。

animation组成部分
  1. 关键帧 keyframes
  2. animation 属性
  3. 个人认为,实现动画的思路也是很重要的一部分
animation语法
animation:动画名 时长 过渡方式 延迟 动画次数 动画方向 填充模式 是否暂停;

假设要做一个让div先向左平移150像素,再向下平移150像素,动画延迟1秒后开始,并且在2秒内完成,动画完成后还要一直自动的重复这样的动画:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>demo</title>
  <style>
    #demo {
  width: 50px;
  height: 50px;
  background-color: red;
  animation: go 2s linear 1s infinite alternate;
}

@keyframes go {
  0%{
    transform:none;
  }
  50%{
    transform:translateX(150px);
  }
  100%{
    transform:translateX(150px) translateY(150px);
  }
}
  </style>
</head>
<body>
   <div id="demo"></div>
  <button id="start">开始</button>
</body>
</html>

如果想添加一个暂停和继续的功能,怎么实现呢?

实现思路:animation每个属性都有对应的属性名,比如控制动画暂停和继续的属性名是animation-play-state,知道属性名和属性值后,就可以通过修改属性值来实现功能:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>demo</title>
    <style>
        #demo {
            width: 50px;
            height: 50px;
            background-color: red;
        }

        #demo.begin {
            animation: go 2s linear 1s infinite alternate;
        }

        @keyframes go {
            0% {
                transform: none;
            }

            50% {
                transform: translateX(150px);
            }

            100% {
                transform: translateX(150px) translateY(150px);
            }
        }
    </style>
</head>

<body>
    <div id="demo"></div>
    <br>
    <button id="start">开始</button>
    <button id="pause">暂停</button>
    <button id="goOn">继续</button>

    <script>
        start.onclick = function () {
            demo.classList.add('begin');
        }
        pause.onclick = function () {
            demo.style.animationPlayState = 'paused';
        }
        goOn.onclick = function () {
            demo.style.animationPlayState = 'running';
        }
        //注意:animation-play-state 和 animationPlayState的书写方式的区别
    </script>
</body>

</html>

浏览器渲染原理

有很多方法可以完成动画效果的实现,但是我们在开发时,性能也是需要重视的一点。要想改善性能,就一定需要知道浏览器的渲染原理:

浏览器渲染过程

  1. 根据HTML构建HTML树 → DOM树
  2. 根据CSS构建CSS树 → CSSOM树
  3. 把DOM树和CSSOM树合并成渲染树 → Render Tree
  4. Layout(布局):文档流、盒模型、计算大小和位置等
  5. Paint(绘制):边框颜色、文字颜色、阴影等
  6. Composite(合成):根据层叠关系把画面展示出来

如何更新样式

我们可以使用CSS里的hover也可以完成更新样式,但是更多的还是用JS来更新样式。一般有三种更新样式的方式:

  1. JS/CSS > 样式 > 布局 > 绘制 > 合成

    所有的渲染流程都走一遍,比如 div.remove(),把div这个节点删除了,一个元素删除后就不占用空间了,那么下面其他的元素就会来替代,因此需要重新布局、绘制和合成。

  2. JS/CSS > 样式 > 绘制 > 合成

    跳过布局,比如div.style.background = 'red' 只改了一个背景颜色,位置并没有改变,所以不用重新布局就可以直接跳过,重新绘制合成即可。

  3. JS/CSS > 样式 > 合成

    跳过布局和绘制,比如改变transform,只是改了一些效果,布局和样式都没有变动,所以可以直接跳过布局和绘制,直接合成即可。注意这种方式必须要在全屏的情况下才能看到,在iframe里查看是存在问题的。

动画优化的小经验

  1. JS优化:用 requestAnimationFrame 代替 translate
  2. CSS优化:用 will-change 或者 translate 代替定位做的效果

一些想法

同样一个动画效果,在CSS里可以有很多种不同的实现方式。有一个需求后,个人觉的,比起方法和语法,更重要的是自己的创意和思路,有了不同的创意和思路后,才能做出更有创意、更符合自己的东西。同时也不得不感叹于CSS的博大精深,自己要学的东西还很多,要走的路还很远。