记录---如何强制用户看完广告?

🧑‍💻 写在开头

点赞 + 收藏 === 学会🤣🤣🤣

场景

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>
    )
};

参考

can i use 查看浏览器支持版本

MDN关于requestAnimationFrame的说明

ahooks中useRefInterval源码

本文转载于:https://juejin.cn/post/7461206048140361754

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

posted @ 2025-03-14 17:05  林恒  阅读(86)  评论(0)    收藏  举报