动画是如何形成的
比较较真的来说,其实不存在真正意义上的动画,我们看到的动画只是很多静止的画面。之所以我们会觉的画面是动态的,是因为人脑的一个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秒内完成:
transition:width 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>
关于animation的总结
animation是动画的意思,要在网页里实现一个动画效果,相比于transition来说,animation往往是用的更多的。
animation组成部分
- 关键帧 keyframes
- animation 属性
- 个人认为,实现动画的思路也是很重要的一部分
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>
浏览器渲染原理
有很多方法可以完成动画效果的实现,但是我们在开发时,性能也是需要重视的一点。要想改善性能,就一定需要知道浏览器的渲染原理:
浏览器渲染过程
- 根据HTML构建HTML树 → DOM树
- 根据CSS构建CSS树 → CSSOM树
- 把DOM树和CSSOM树合并成渲染树 → Render Tree
- Layout(布局):文档流、盒模型、计算大小和位置等
- Paint(绘制):边框颜色、文字颜色、阴影等
- Composite(合成):根据层叠关系把画面展示出来
如何更新样式
我们可以使用CSS里的hover也可以完成更新样式,但是更多的还是用JS来更新样式。一般有三种更新样式的方式:
-
JS/CSS > 样式 > 布局 > 绘制 > 合成
所有的渲染流程都走一遍,比如 div.remove(),把div这个节点删除了,一个元素删除后就不占用空间了,那么下面其他的元素就会来替代,因此需要重新布局、绘制和合成。
-
JS/CSS > 样式 > 绘制 > 合成
跳过布局,比如div.style.background = 'red' 只改了一个背景颜色,位置并没有改变,所以不用重新布局就可以直接跳过,重新绘制合成即可。
-
JS/CSS > 样式 > 合成
跳过布局和绘制,比如改变transform,只是改了一些效果,布局和样式都没有变动,所以可以直接跳过布局和绘制,直接合成即可。注意这种方式必须要在全屏的情况下才能看到,在iframe里查看是存在问题的。
动画优化的小经验
- JS优化:用 requestAnimationFrame 代替 translate
- CSS优化:用 will-change 或者 translate 代替定位做的效果
一些想法
同样一个动画效果,在CSS里可以有很多种不同的实现方式。有一个需求后,个人觉的,比起方法和语法,更重要的是自己的创意和思路,有了不同的创意和思路后,才能做出更有创意、更符合自己的东西。同时也不得不感叹于CSS的博大精深,自己要学的东西还很多,要走的路还很远。