记录---如何强制用户看完广告?
🧑💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣
场景
web端有这样一种场景,广告方投放了一些广告,并要求用户停留在当前页面一定时间(例如看完一段30秒的广告)才能收到活动奖励。
用户可能会打开广告后切到其他tab页或者最小化当前页面,去做自己的事情,如何强制用户停留在当前页面,看完广告?
直接使用定时器
一开始比较容易想到的方案是在页面组件中设置一个定时器,组件渲染时开始计时,倒计时结束后调用成功的回调,代码如下
import { useEffect, useRef, useState } from "react"; const Comp = () => { const [seconds, setSeconds] = useState(5) const timer = useRef<NodeJS.Timeout | null>(null) useEffect(() => { timer.current = setInterval(() => { setSeconds(s => s - 1) }, 1000); return () => { timer.current && clearInterval(timer.current) } }, []) const callback = () => { console.log('倒计时结束,获得奖励') } useEffect(() => { if(seconds <= 0) { callback() timer.current && clearInterval(timer.current) } }, [seconds]) return ( <div> 观看 {seconds} 秒广告后获得现金奖励!!! </div> ) };
代码运行后也确实起到了倒计时的作用,但是用户切到其他页面时,倒计时依然在继续。
更好的方案
有时候用户可能打开广告页之后又切到了其他页面,或者将页面最小化,然后去做自己的事情,如何强制用户停留在当前页面看完广告呢? 问题其实出在setInterval
上,简单的定时器即使页面不在前台,也依然会默默执行。
有没有一旦用户离开当前页,倒计时就停止执行的方法呢?
有!答案就是requestAnimationFrame
。由于浏览器的性能优化的考虑,当页面不在前台时(被最小化或者隐藏),浏览器会暂停requestAnimationFrame
的执行。
所以我们可以用requestAnimationFrame
实现setInterval
,实现如下
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; };
解释
入参: 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> ) };