了解过事件循环机制的朋友应该知道,siteTimeout
和setInterval
并不是精准的时间间隔,他们要等待其他优先的执行队列执行完成以后才能继续执行。
于是就引入了一个新的动画执行方式-- window.requestAnimationFrame()
。它告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
它采用的是系统时间间隔,以保证最佳的绘制效率,不会因为时间过短造成过度绘制,也不会因为时间间隔太长,产生动画卡顿的现象。让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果
特点
requestAnimationFrame
会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
在隐藏或不可见的元素中,requestAnimationFrame
将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
requestAnimationFrame
是由浏览器专门为动画提供的API
,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
应用
requestAnimationFrame
的用法与settimeout
很相似,只是不需要设置时间间隔而已。requestAnimationFrame
使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame
用于取消这个函数的执行
let retID = requestAnimationFrame(callback);
取消的话可直接使用canceAnimationFrame
来进行取消即可
cancelAnimationFrame(retID)
先来看个setInterval动画的例子
效果如图:
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title></title>
<style>
* {
margin: 0;
padding: 0;
}
#box {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
overflow: hidden;
}
.item-container {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
height: 500px;
width: 100px;
}
.item {
width: 50px;
height: 50px;
background-color: blue;
border-radius: 50%;
margin-top: 10px;
z-index: 100;
}
.open {
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
}
</style>
</head>
<body>
<button id="btn1">改变高度</button>
<div id="box">
<div class="open">展开</div>
<div class="item-container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
</div>
</div>
<script>
window.addEventListener("load", function (ev) {
var box = document.getElementById("box");
var flag = true;
// 执行动画,改变盒子高度
document.getElementById("btn1").addEventListener("click", function (ev) {
if (flag) {
buffer(box, "height", 500);
flag = false;
} else {
buffer(box, "height", 100);
flag = true;
}
});
});
// 获取当前元素样式
function getStyleAttr(obj, attr) {
if (obj.currentStyle) {
// IE 和 opera
return obj.currentStyle[attr];
} else {
return window.getComputedStyle(obj, null)[attr];
}
}
// 迅速动画函数
function buffer(eleObj, attr, target) {
//1.1先清后设
clearInterval(eleObj.timer);
var speed = 0,
begin = 0;
//1.2设置定时器
eleObj.timer = setInterval(function () {
//获取动画属性的初始值
begin = parseInt(getStyleAttr(eleObj, attr));
speed = (target - begin) * 0.2;
speed = target > begin ? Math.ceil(speed) : Math.floor(speed);
eleObj.style[attr] = begin + speed + "px";
if (begin === target) {
clearInterval(eleObj.timer);
}
}, 20);
}
</script>
</body>
</html>
用requestAnimationFrame
代替setInterval
重写buffer
函数
// 动画函数
buffer(eleObj, attr, target) {
// 先清后设
cancelAnimationFrame(eleObj.timer)
let speed = 0
let begin = 0
let _this = this
eleObj.timer = requestAnimationFrame(function fn() {
begin = parseInt(_this.getStyleAttr(eleObj, attr))
// 动画速度
speed = (target - begin) * 0.9
speed = target > begin ? Math.ceil(speed) : Math.floor(speed)
eleObj.style[attr] = begin + speed + "px"
eleObj.timer = requestAnimationFrame(fn)
if (begin === target) {
cancelAnimationFrame(eleObj.timer);
}
})
},
getStyleAttr(obj, attr) {
if (obj.currentStyle) {
// IE 和 opera
return obj.currentStyle[attr];
} else {
return window.getComputedStyle(obj, null)[attr];
}
}