解释
入参: callback: 一个无参数的回调函数,当达到延迟时间时将调用这个函数。 delay: 一个表示延迟时间的数字(以毫秒为单位)。
返回值: obj 是一个对象,其中包含一个名为 timer 的属性。这个属性用于存储 requestAnimationFrame 的标识符,以便取消动画。
start 变量记录了当前的时间戳(以毫秒为单位),这是用来计算时间差的。
requestAnimationFrame会在浏览器渲染一帧的空闲时间执行参数的回调,这里在回调cb的中间又加入了requestAnimationFrame(cb),即通过递归实现每一帧都会执行cb函数,直到累积的时间大于传入的延迟时间,这里的延迟时间即setInterval的第二个参数,当累积时间达到delay之后执行setInterval的第一个参数,同时更新start时间戳,从而模拟出setInterval的效果。
值得注意的点是,该函数返回的其实是一个对象,而不是requestAnimationFrame的返回值(标识符),这里其实利用了闭包,使得返回的是同一个倒计时的标识,方便清空interval的函数使用。
在组件中使用
利用我们实现的setInterval,用来代替原生的setInterval,页面在最小化或者用户切到其他tab页时,倒计时不再继续运行,直到用户打开该页面,实现强制用户停留在当前页面观看广告。
import { useEffect, useRef, useState } from "react";
const Comp = () => {
const [seconds, setSeconds] = useState(30)
const intervalRef = useRef<{timer: number} | null>(null)
const mySetInterval = (callback: () => void, delay: number = 0) => {
let start = new Date().getTime();
const obj = {
timer: 0
}
const cb = () => {
const current = new Date().getTime();
if (current - start >= delay) {
callback();
start = new Date().getTime();
}
obj.timer = requestAnimationFrame(cb);
};
obj.timer = requestAnimationFrame(cb);
return obj;
};
useEffect(() => {
intervalRef.current = mySetInterval(() => {
setSeconds(s => s - 1)
}, 1000);
return () => {
intervalRef.current && cancelAnimationFrame(intervalRef.current.timer)
}
}, [])
const callback = () => {
console.log('倒计时结束,获得奖励')
}
useEffect(() => {
if (seconds <= 0) {
callback()
intervalRef.current && cancelAnimationFrame(intervalRef.current.timer)
}
}, [seconds])
return (
<div>
观看 {seconds} 秒广告后获得现金奖励!!!
</div>
)
};