显示器都有一个刷新率。一般的显示器刷新率都是60Hz。相当于浏览器在每秒可以重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过了这个频率,用户体验也不会有提升。所以平滑动画的最佳循环间隔是1s/60,约等于17ms。这个循环间隔重绘的动画是平滑的,因为这个速度最接近浏览器的最高限速。为了适应17ms的循环间隔,多重动画可能需要加以节制,以便不会完成得太快。
虽然使用setInterval比setTimeout相率更高,但是两种方法都不够精准。他们都无法去自适应浏览器的重绘。
window.requestAnimationFrame是自适应浏览器的重绘,速度是由浏览器的帧决定的,不同浏览器自行决定最佳的帧效率。
<!DOCTYPE html>
<html>
<head>
<title>window.requestFrame</title>
</head>
<body>
<div style="border: 1px ; 200px;height: 200px;background-color: pink;position: absolute;" id="dv">
</div>
<script type="text/javascript">
var divPosition = 0;
var div = document.getElementById('dv');
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
div_width = parseInt(div.style.width);
function step(timestamp) {
divPosition+=10;
div.style.left = divPosition + 'px';
if(divPosition < window.screen.availWidth - div_width){
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
</script>
</body>
</html>
兼容写法:
if(!window.requestAnimationFrame) {
window.requestAnimationFrame = (window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
var self = this, start, finish;
return window.setTimeout(function() {
start = +new Date();
callback(start);
finish = +new Date();
self.timeout = 1000/60 - (finish - start);
}, self.timeout);
});
}
封装window.requestAnimationFrame
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
通过window.requestAnimationFrame实现平滑的返回顶部。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>返回顶部</title>
<style>
.box {
margin: 0 auto;
width: 100%;
height: 5000px;
}
.box1 {
background: #b94a48;
}
.box2 {
background: #fb8c00;
}
.box3 {
background: #669900;
}
.box4 {
background: #c0a16b;
}
.top {
position: fixed;
right: 20px;
bottom: 20px;
width: 40px;
height: 40px;
background: #8dc7ff;
border-radius: 50%;
cursor: pointer;
}
</style>
<script>
window.requestAnimationFrame = (function () {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 6000 / 60)
}
})()
window.cancelAnimFrame = (function () {
return window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
window.msCancelAnimationFrame ||
function (callback) {
window.clearTimeout(callback)
}
})()
function scrollToTop() {
let top = window.pageYOffset
const duration = 320
const step = top / (duration / (1000 / 60)) >> 0
const fn = () => {
if (top >= 0) {
top -= step
window.scrollTo(0, top)
fn.rafTimer = window.requestAnimationFrame(fn)
} else {
window.scrollTo(0, 0)
window.cancelAnimationFrame(fn.rafTimer)
}
}
fn.rafTimer = window.requestAnimationFrame(fn)
}
</script>
</head>
<body>
<div class="box box1"></div>
<div class="box box2"></div>
<div class="box box3"></div>
<div class="box box4"></div>
<div class="top" onclick="scrollToTop()"></div>
</body>
</html>