动画
这篇文章咱们来聊聊动画的实现方法。前端动画总体上分为两类 css 动画和 JS 动画,css 动画就是通过设置 css 属性实现,而 JS 动画就是利用 JavaScript 操控 DOM 的能力实现动画。
css动画
这里简单介绍一下 css 动画
transition
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>
#test {
width: 100px;
height: 100px;
background-color: aqua;
}
#test:hover {
width: 200px;
transition: width 1s;
}
</style>
</head>
<body>
<div id="test"></div>
</body>
</html>
animation
animation 动画就很好的解决了上述问题,它能够自动执行、重复执行,也能够同时设置多个属性的变化,并且还可以设置多个中间态。
<!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>
#test {
width: 100px;
height: 100px;
background-color: aqua;
animation: animate 1s linear;
}
@keyframes animate {
0% {
width: 100px;
height: 100px;
}
50% {
width: 160px;
height: 160px;
}
100% {
width: 300px;
height: 300px;
}
}
</style>
</head>
<body>
<div id="test"></div>
</body>
</html>
但是因为 css 动画只能在现有的属性上让我们设置不同的值,然后执行。这就给 css 动画带来了限制,因为在开发中可能会遇到变化的属性是不固定的情况,此时 css 动画就很乏力了。并且 css 不能定义很复杂的动画。
JS 动画
因为 css 动画由以上诸多限制,所以有的时候我们不得不使用 JS 来控制动画流程。
早期JS动画
早期的 JS 动画是通过 settimeout 和 setInterval 执行的。这里时间间隔设置为 16.6ms 的原因是一般电脑的刷新率为 60 hz,也就是说 1s 屏幕会刷新 60 次,1000ms / 60 近似等于 16.6ms,这样理想情况下的动画最为平滑流畅。
setInterval 动画
<!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>
<script src="./test.js"></script>
<style>
#test {
width: 100px;
height: 100px;
background-color: aqua;
}
</style>
</head>
<body>
<div id="test"></div>
<script>
const oDiv = document.getElementById('test');
let width = 100;
const animate = setInterval(() => {
width++;
oDiv.style.width = width + 'px';
if (width >= 200) {
clearInterval(animate);
}
}, 16.6)
</script>
</body>
</html>
setTimeout 动画
<!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>
<script src="./test.js"></script>
<style>
#test {
width: 100px;
height: 100px;
background-color: aqua;
}
</style>
</head>
<body>
<div id="test"></div>
<script>
const oDiv = document.getElementById('test');
let width = 100;
const animate = () => {
width++;
oDiv.style.width = width + 'px';
if (width <= 200) {
setTimeout(animate, 16.6);
}
}
setTimeout(animate, 16.6);
</script>
</body>
</html>
上面说了时间间隔为 16.6ms 的动画最为流畅平滑,但是使用 setInterval 和 setTimeout 并一定能满足要求,我们知道 JS 的事件循环机制(不知道的可以看看这篇博客JS基础——捋一捋 JavaScript 事件循环机制), setInterval 和 setTimeout 是两个宏任务,这样就会每次间隔 16.6ms 就将回调函数推进宏任务队列,等待执行,注意这里并不是间隔 16.6ms 一定会执行回调函数,因此若一轮事件循环中同步任务执行时间过长就会导致动画卡顿。
requestAnimationFrame
因为 setInterval 和 setTimeout 实现动画的缺陷,我们就在思考,因为屏幕刷新率电脑的一个物理特性,并不会随你使用的编程语言变化而变化,那么有没有可能拿到屏幕的刷新时机,在每次刷新的时候执行一次回调函数呢?
基于这个想法,有人提出了 requestAnimationFrame 方法,接收一个函数参数,这个函数参数就是每次屏幕刷新前要执行的回调函数。requestAnimationFrame 方法会通知浏览器 JS 要执行动画了,在屏幕每次刷新前调用函数参数。
requestAnimationFrame 方法本质上是浏览器有一个没有长度限制的队列,当调用 requestAnimationFrame 方法就会向队列中推入回调函数,该回调函数会在下一次重绘之前调用。个人理解它和 setInterval 原理上非常类似,只是回调函数的执行时机不同。
requestAnimationFrame 用法
它的用法和 setTimeout 的用法很像。但是它不用传入时间间隔(因为时间间隔是固定的),它的返回值是一个整数,是回调列表中的唯一 ID,作用是用来取消动画的。
<!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>
<script src="./test.js"></script>
<style>
#test {
width: 100px;
height: 100px;
background-color: aqua;
}
</style>
</head>
<body>
<div id="test"></div>
<script>
const oDiv = document.getElementById('test');
let width = 100;
const animate = () => {
width++;
oDiv.style.width = width + 'px';
if (width <= 200) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
</script>
</body>
</html>
cancelAnimationFrame
本方法是用来取消动画的。上面的 demo 使用本方法如下:
<!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>
<script src="./test.js"></script>
<style>
#test {
width: 100px;
height: 100px;
background-color: aqua;
}
</style>
</head>
<body>
<div id="test"></div>
<script>
const oDiv = document.getElementById('test');
let width = 100, end = 0;
const animate = () => {
width++;
oDiv.style.width = width + 'px';
end = window.requestAnimationFrame(animate);
if (width >= 200) {
window.cancelAnimationFrame(end);
}
}
window.requestAnimationFrame(animate);
</script>
</body>
</html>
requestAnimationFrame 还有两个优势:
- 当你在执行动画时,但是你并没有浏览动画那个页面,那么该函数就会暂停执行,也就是动画会暂停,当你切回来后会继续执行动画,这将提高电脑的性能,让你的电脑有精力去做别的事。
- 当在一帧中频繁操作 DOM 后,使用本方法 DOM 将只会被修改一次,最后的值就是所有 DOM 操作最后的结果,相当于一个节流的操作了。
测测电脑刷新率
运行将下面这段代码,你就可以得到你的电脑刷新率哦,可以多运行几次,更准确。
let count = 0, m = null;
function refresh() {
count++;
m = window.requestAnimationFrame(refresh);
}
window.requestAnimationFrame(refresh)
function getResult() {
console.log('你的电脑刷新率为:' + count + 'Hz');
window.cancelAnimationFrame(m);
}
setTimeout(getResult, 1000)
总结
requestAnimationFrame 方法解决了传统 JS 动画的问题,它的 API 也很简单,同时还有节流和离开暂停动画的效果,实在优雅啊。我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。