基于window.requestAnimationFrame的精准定时器/简单轮询

181 阅读2分钟

备注:因为有使用到window挂载,不同环境node可以对比切为global或者其他公用地方都可以

定时器

基本描述:

1.基于window.requestAnimationFrame函数精准渲染,基本是1s60帧--16.7ms一帧--所以大概误差在16.7ms内

2.名称不会冲突--基本名称+uuid标识+时间戳

3.封装了,使用方便

const requestAnimationFrameTimer = (callbak, timer) => {
    (() => {
        /**
         * 执行函数
         * @param {根时间} reqTimerTime
         * @param {requestAnimationFrame生成的id,取消的时候可以使用} reqTimerId
         * @param {时间阀--判断时间是否到达的标准} timer
         */
        const dealFn = (reqTimerTime, reqTimerId, timer) => {
            let tempTime = Date.now();
            if (tempTime - window[reqTimerTime] >= timer) {
                //是否满足时间
                console.log(`时间够了${tempTime}`);
                window.cancelAnimationFrame(window[reqTimerId]); //取消requestAnimationFrame函数
                callbak();
            } else {
                setReqFrameId(reqTimerTime, reqTimerId, timer); //不满足重新执行requestAnimationFrame
                console.log(`时间不够${tempTime}:${tempTime - window[reqTimerTime]}`);
            }
        };
        /**
         * 为了构建一个随机名称
         * @returns
         */
        const guid = () => {
            return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
                var r = (Math.random() * 16) | 0,
                    v = c == "x" ? r : (r & 0x3) | 0x8;
                return v.toString(16);
            });
        };
        /**
         * 设置requestAnimationFrame生成的标识
         * @param {根时间} reqTimerTime
         * @param {requestAnimationFrame生成的id,取消的时候可以使用} reqTimerId
         * @param {时间阀--判断时间是否到达的标准} timer
         */
        const setReqFrameId = (reqTimerTime, reqTimerId, timer) => {
            window[reqTimerId] = window.requestAnimationFrame(() =>
                dealFn(reqTimerTime, reqTimerId, timer)
            );
        };

        //初次执行
        let nameTimestamp=Date.now()
        let guidName= guid().split("-").join("")+nameTimestamp
        let reqTimerTime = "requestAnimationFrametime" + guidName;
        let reqTimerId = "requestAnimationFrameId" +guidName;
        window[reqTimerTime] = Date.now();
        setReqFrameId(reqTimerTime, reqTimerId, timer);
    })();
};

requestAnimationFrameTimer(() => {
    console.log("我是定时器callback");
}, 1500);


轮询
//基于以上封装的requestAnimationFrameTimer定时器基础--设计轮式机制
//其实这里用requestAnimationFrame去实现轮询有点奢侈,比如如果你项目是与物理设备交互,想精确的高频率去刷新来轮询,可以用这个。不然要求不严格的话,setInterval也是不错选择

const requestAnimationFrameTimerLoop = (callbak, timer) => {
    (() => {
        /**
         * 执行函数
         * @param {根时间} reqTimerTime
         * @param {requestAnimationFrame生成的id,取消的时候可以使用} reqTimerId
         * @param {时间阀--判断时间是否到达的标准} timer
         */
        const dealFn = (reqTimerTime, reqTimerId, timer) => {
            if(sessionStorage.getItem('reqFrameClose')){
                return
            }
            let tempTime = Date.now();
            if (tempTime - window[reqTimerTime] >= timer) {
                //是否满足时间
                console.log(`时间够了${tempTime}`);
                window.cancelAnimationFrame(window[reqTimerId]); //取消requestAnimationFrame函数
                callbak();
                console.log('继续执行');
            } else {
                setReqFrameId(reqTimerTime, reqTimerId, timer); //不满足重新执行requestAnimationFrame
                console.log(`时间不够${tempTime}:${tempTime - window[reqTimerTime]}`);
            }
        };
        /**
         * 为了构建一个随机名称
         * @returns
         */
        const guid = () => {
            return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
                var r = (Math.random() * 16) | 0,
                    v = c == "x" ? r : (r & 0x3) | 0x8;
                return v.toString(16);
            });
        };
        /**
         * 设置requestAnimationFrame生成的标识
         * @param {根时间} reqTimerTime
         * @param {requestAnimationFrame生成的id,取消的时候可以使用} reqTimerId
         * @param {时间阀--判断时间是否到达的标准} timer
         */
        const setReqFrameId = (reqTimerTime, reqTimerId, timer) => {
            if(sessionStorage.getItem('reqFrameClose')){
                return
            }
            window[reqTimerId] = window.requestAnimationFrame(() =>
                dealFn(reqTimerTime, reqTimerId, timer)
            );
        };

        //初次执行
        let nameTimestamp=Date.now()
        let guidName= guid().split("-").join("")+nameTimestamp
        let reqTimerTime = "requestAnimationFrametime" + guidName;
        let reqTimerId = "requestAnimationFrameId" +guidName;
        window[reqTimerTime] = Date.now();
        setReqFrameId(reqTimerTime, reqTimerId, timer);
    })();
};

const loop=function(type){
    try {
        if(type=='start'){
            requestAnimationFrameTimerLoop(() => {
                //执行自己逻辑--比如与物理设备交互
                console.log("我是定时器callback");
                //处理完后需要重新触发loop
                loop('start')
            }, 1500);
        }
        if(type=='close'){
            /**
             * 为什么这里用缓存来去做一个拦截取消,主动调用 window.cancelAnimationFrame不行吗
             * 1.因为cancelAnimationFrame调用后,其实requestAnimationFrame可能会刚好走到重新生成/调用requestAnimationFrame,导致没有取消掉
             * 2.所以这里可以做一个静态标识判断处理,主要捕捉这种标识,就不再去生成requestAnimationFrame
             */
            sessionStorage.setItem('reqFrameClose',true)
        }
    } catch (error) {
        loop('start')
    }
}

loop('start')
setTimeout(() => {
    loop('close')         
}, 4500);