requestAnimationFrame请求动画帧

requestAnimationFrame是什么

window.requestAnimationFrame,根据W3C标准的解释,我们可以通过调用它来告诉浏览器,需要执行一个动画,并且要求浏览器在 下次重绘之前 调用指定的回调函数更新动画。所以该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

下次重绘之前 是啥意思

怎么去理解 下次重绘之前 呢,先来看下这两行代码

document.body.appendChild(el)
el.style.display = 'none'

这种代码总会让我感觉有点别扭,我会考虑el这个元素会不会“一闪而过”,

通常我都习惯把这两行代码倒过来写。这么到底写会不会出问题呢?答案是不会的。
 

Js操作dom到浏览器渲染的过程如下:

 

JavaScript:JavaScript实现动画效果,DOM元素操作等。

Style(css样式):收集DOM元素对应的CSS。

Layout(布局):计算DOM元素在最终屏幕上显示的大小和位置。由于web页面的元素布局是相对的,所以其中任意一个元素的位置发生变化,都会联动的引起其他元素发生变化,这个过程叫reflow(回流)。

Paint(绘制):在多个层上绘制DOM元素的的文字、颜色、图像、边框和阴影等。

Composite(渲染层合并):按照合理的顺序合并图层然后显示到屏幕上。

大部分从Style 到 Composite 这个过程是跟屏幕的刷新频率有关的,即如果屏幕刷新频率是60Hz的话,Style 到 Composite 这个过程的循环间隔是(1000/60)ms。所以若干行js代码如果运行在同一帧内,下一帧反应到页面上的状态就是dom最新的样式。

那么demo1的代码对应的浏览器渲染过程就像如下图:

 想象每个长长的矩形都是一帧,每一帧的开头会执行Style -> Composite这个过程,如果task1、2、3操作了同一个dom的同一个属性,那么最终在执行Style的时候只有上一帧的task3会生效。

document.body.appendChild(el)
setTimeout(() => {
   el.style.display = 'none'
}, 20)

如果我们把dome1改成如上代码,然后再运行试试,就出现了“一闪而过”的现象。原因是我们在第一帧渲染完成后执行document.body.appendChild(el),第二帧的开头就会将el元素渲染出来,第二帧渲染完成后再执行el.style.display = 'none',第三帧的开头就会反应到页面上。

回头再看requestAnimationFrame运行在 下次重绘之前 这句话,可以理解为运行在在Javascript与Style之间的一个函数(暂不考虑浏览器差异,以W3C为标准)。如果把动画效果都打包进RAF的话, 就降低了因为不断操作同一dom产生动画不流畅(掉帧)的概率,渲染过程也会显得非常科学。当然,不可避免的在会有少数相关动画任务漏在了RAF回调之外执行,但问题也不会太大。RAF方案如下图:

 需要注意的是RAF是一个macrotask(宏任务),callback中的代码中不宜包含复杂算法,否则还是会阻塞进程。想像一下如果第1帧的RAF回调算法过复杂导致原本1帧的任务用了2帧的时间才完成,那么第二帧的计算将会被拖到原本第三帧的位置,这样的话就相当于降低了页面刷新频率,可能会导致动画总时间变长。图解如下:

requestAnimationFrame基本用法

基本语法:

window.requestAnimationFrame(callback)。

参数:

callback,传入的函数可以通过js来控制下次重绘之前dom元素的css样式。callback默认接收一个时间参数,其数值等于performance.now(),一般用来控制动画效果。

返回值:

一个非零long整数,是回调列表中唯一的标识,没别的意义,跟setTimeout的返回值类似。我们可以传这个值给window.cancelAnimationFrame()取消这个回调函数。

const black = document.getElementById('black')
        let animationId = null
        let startTime = 0
        let duration = 6000
        let length = 600
        let pass = 0

        const getPosition = (x) => {
            return Math.ceil(length * x)
        }
        const animations = (time) => {

            let left = 0

            if (time) {
                if (time - startTime > duration + 100) {
                    startTime = time - pass
                }
                pass = time - startTime
                left = getPosition(pass / duration)
            }

            if (left > length) {
                black.style.left = `${length}px`
                cancelAnimationFrame(animationId)
            } else {
                black.style.left = `${left}px`
                animationId = requestAnimationFrame((timeStamp) => {
                    if (!startTime) startTime = timeStamp
                    animations(timeStamp)
                })
            }
        }
        black.addEventListener('click', () => {
            animations()
        })

上边代码中用setInterval或者setTimeout实现其实很简单,但这种匀速动画,属于最朴实无华的那种了,对于匀变速动画区别就体现的很明显了。很久以前瞄过几眼jquery代码,它的动画是用setTimeout完成的,听说后来也改成了RAF。setTimeout不无法把控页面刷新的频率,当完成匀变速动画或者速度变化复杂的动画的计算量和动画效果,也不如RAF。

requestAnimationFrame优劣

优势:

  1. 经过浏览器优化,动画更流畅。

  2. 间隔时间固定,只需要计算每一帧的变化。

  3. 窗口没激活时,动画将停止,省计算资源。

  4. 综合前三点优势,这种方案更省电,对移动终端来说会更友好。

劣势:

  • 不同浏览器RAF的调用时机是不同的。有的浏览器的渲染过程如下图:

    虽然调用时机不同但区别也并不是很大,只是每次动画开始执行的时候会有一帧的延迟,尝试了自己常用的三个浏览器尝试了一下。

    firefox:RAF放在Style之前的执行;
    Chrom:网上查到RAF放在Style之前执行;
    Safari: RAF放在Compsite之后执行的;

  • requestAnimationFrame兼容性问题及解决方法

caniuser上搜索这个api,这个支持率还是非常可观的,但是为了满足个别怪物还需要加点代码来解决一下。

下面这段代码是搜来的,读起来并不是那么的困难,其实就是判断一下当前环境下window对象有没有requestAnimationFrame这个方法,没有的话就加'webkit'和'moz'前缀试试,如果还是没有的话那就用setTimeout来模拟一个。cancelAnimationFrame也是同理,实在没有的话就用clearTimeout来模拟。

if (!Date.now) Date.now = function() { return new Date().getTime(); };

(function() {
    'use strict';
    
    var vendors = ['webkit', 'moz'];
    for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
        var vp = vendors[i];
        window.requestAnimationFrame = window[vp+'RequestAnimationFrame'];
        window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame']
                                   || window[vp+'CancelRequestAnimationFrame']);
    }
    if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy
        || !window.requestAnimationFrame || !window.cancelAnimationFrame) {
        var lastTime = 0;
        window.requestAnimationFrame = function(callback) {
            var now = Date.now();
            var nextTime = Math.max(lastTime + 16, now);
            return setTimeout(function() {callback(lastTime = nextTime); },
                              nextTime - now);
        };
        window.cancelAnimationFrame = clearTimeout;
    }
}());

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值