介绍 RAF

window.requestAnimationFrame

requestAnimationFrame 是专门为实现高性能的帧动画而设计的一个API

  • 想要动画流畅,更新频率要 60帧/s ,即 16.67ms 更新一次视图

  • setTimeout 要手动控制频率, 而 RAF 浏览器会自动控制

  • 后台标签或隐藏 iframe 中,RAF 会暂停,而 setTimeout 依然执行

setTimeout和RAF示例一

3s 把宽度 100px 变成 640px,即增加 540px

60 帧/s, 那么3s 可变化 180 帧,每次变化 3px

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>test</title>
</head>
<style>
#div1{
    width: 100px;
    height: 100px;
    background: red;
    position: absolute;
    left: 0;
    top: 0;
    zoom: 1;
}
</style>
<body>
	<div id="div1">
      <p>hello</p>
	</div>
<script>  
window.onload = function() {

	const div1 = document.getElementById('div1')
	let curWidth = 100
	const maxWidth = 640

	// setTimeout
	// function animate() {
	//     curWidth = curWidth + 3
	//     div1.style.width = curWidth + 'px'
	//     if (curWidth < maxWidth) {
	// 	    // 自己控制时间
	//     	setTimeout(animate, 16.7) 
	//     }
	// }
	// animate()

    // RAF
	function animate2() {
		curWidth = curWidth + 3
	    div1.style.width = curWidth + 'px'
	    if (curWidth < maxWidth) {
	    	// 时间不用自己控制
	    	requestAnimationFrame(animate2)
	    }
	}
	requestAnimationFrame(animate2)
}

</script>
</body>
</html>

setInterval和RAF示例一

将一个元素不停地做左右运动

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>test</title>
</head>
<style>
#div1{
    width: 100px;
    height: 100px;
    background: red;
    position: absolute;
    left: 0;
    top: 0;
    zoom: 1;
}
</style>
<body>
	<div id="div1">
      <p>hello</p>
	</div>
<script>  
    var e = document.getElementById("div1");
	var flag = true;
	var left = 0;

	function render() {
	    if (flag == true) {
	        if (left >= 100) {
	            flag = false
	        }
	        e.style.left = ` ${left++}px`
	    } else {
	        if (left <= 0) {
	            flag = true
	        }
	        e.style.left = ` ${left--}px`
	    }
	}

    // setInterval效果
	// setInterval(function(){
	//      render()
	// }, 1000/60)

	// requestAnimationFrame 效果
    (function animloop() {
        render();
        window.requestAnimationFrame(animloop);
    })();
</script>
</body>
</html>

window.cancelAnimationFrame()

怎么停止 requestAnimationFrame ?

cancelAnimationFrame() 接收一个参数 requestAnimationFrame 默认返回一个id,cancelAnimationFrame 只需要传入这个id就可以停止了。

    var e = document.getElementById("div1");
    var flag = true;
    var left = 0;
    var rafId = null

    function render() {
        if (flag == true) {
            if (left >= 100) {
                flag = false
            }
            e.style.left = `${left++}px`
        }else{
            if(left <= 0){
                flag = true
            }
            e.style.left = `${left--}px`
        }
    }

    // requestAnimationFrame效果
    (function animloop(time) {
        render();
        rafId = requestAnimationFrame(animloop);
        // 如果left等于50 停止动画
        if (left == 50){
            cancelAnimationFrame(rafId)
        }
    })();

如何将requestAnimationFrame帧率降低?

默认16.7ms 更新一次视图,我们修改为每40ms更新一次

var e = document.getElementById("div1");
    var flag = true;
    var left = 0;
    // 当前执行时间
    var nowTime = 0;
    // 记录每次动画执行结束的时间
    var lastTime = Date.now();
    // 我们自己定义的动画时间差值
    var diffTime = 40;

    function render() {
        if (flag == true) {
            if (left >= 100) {
                flag = false
            }
            e.style.left = `${left++}px`
        } else {
            if (left <= 0) {
                flag = true
            }
            e.style.left = `${left--}px`
        }
    }

    // requestAnimationFrame效果
    (function animloop() {
        // 记录当前时间
        nowTime = Date.now()
        // 当前时间-上次执行时间如果大于 diffTime,那么执行动画,并更新上次执行时间
        if (nowTime - lastTime > diffTime) {
            lastTime = nowTime
            render();
        }
        requestAnimationFrame(animloop);
    })()

RAF Advantage

requestAnimationFrame 比起 setTimeout、setInterval的优势:

1、requestAnimationFrame 由系统来决定回调函数的执行时机。具体一点讲,如果屏幕刷新率是 60Hz,那么回调函数就每 16.7ms 被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了 1000/75=13.3ms,换句话说就是,requestAnimationFrame 的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。

2、CPU节能:使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。而requestAnimationFrame则完全不同,当页面处于未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。

3、函数节流:在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,使用requestAnimationFrame可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。一个刷新间隔内函数执行多次时没有意义的,因为显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来。

如何将requestAnimationFrame帧率降低?