[js] 页面可见性API 监测用户切屏

Page Visibility API

在做考试系统或者网课系统的时候,通常需要监测用户是否隐藏了当前标签页在看其它页面。

Page Visibility API提供了一个事件和两个状态来监测页面可见性,可以用它来判断用户是否切屏。

visibilitychange

这个事件会在页面可见性变化时触发。(隐藏时、打开时)

// 使用 addEventLisitener
document.addEventListener('visibilitychange', (e)=>{
    console.log('visibilityState: ', document.visibilityState);
});

// 或者 onvisibilitychange
document.onvisibilitychange = function(){
    console.log('visibilityState: ', document.visibilityState);
}

document.visibilityState

这个变量有3种值:

  • hidden

    • 浏览器被最小化了;
    • 浏览器打开,但是当前看的是其它标签页;
    • 这个标签页要被卸载了(unload);
  • visible

    • 当前正在看标签页;

    • 当前浏览器处于最小化,但是正在预览页面内容;

      如下图,这种把鼠标移到任务栏图标上的行为也会触发 visibilitychange 事件,且 document.visibilityState 变为 visible。

  • prerender:页面即将或正在渲染,处于不可见状态。

document.hidden

这个状态仅用于兼容,平时应使用document.visibilityState。当document.visibilityStatevisible时,document.hiddenfalse,其余情况下都为 true

iframe

当页面中通过 iframe 嵌入子文档时,iframe节点的display:none;并不会触发子文档的 visibilitychange事件。

子文档的可见性和父文档的可见性保持一致。

示例场景

考试违规切屏次数统计

let violationCount = 0;
const maxViolations = 3; // 设定最大违规次数

document.addEventListener('visibilitychange', function() {
    if (document.visibilityState === 'visible') {
        console.log('考试页面重新可见');
    } else {
        violationCount++;
        console.log(`考试页面不可见,违规次数: ${violationCount}`);

        // 如果违规次数达到上限,触发结束考试的逻辑
        if (violationCount >= maxViolations) {
            endExam();
        } else {
            alert(`警告:请勿离开考试页面!您已违规 ${violationCount} 次,最多允许 ${maxViolations} 次违规。`);
        }
    }
});

function endExam() {
    alert('您已多次离开考试页面,考试结束!');
    // 在此添加结束考试的逻辑,例如提交答案并退出考试界面
    submitExam();
}

function submitExam() {
    // 模拟提交考试结果
    console.log('考试结果已提交');
    // 重定向到考试结束页面或显示结束信息
    window.location.href = '/exam-finished';
}

// 模拟考试开始
console.log('考试开始,请勿离开页面。');

"被动"网课学习

document.addEventListener('visibilitychange', function() {
    if (document.visibilityState === 'visible') {
        video.play();	// 视频播放
    } else {
        video.pause();	// 视频暂停
    }
});

如何避免逃课?

下面的策略可以提高逃课的门槛(用户可能会编写脚本来让视频在页面不可见的情况下也能播放),但是只能说是防君子不防小人。

  1. setInterval定时检查visibilityState,缺点是定时器在页面不可见的情况下执行频率和设定的时间可能不一样。

    const videoElement = document.querySelector('video');
    
    function checkVisibility() {
        if (document.visibilityState === 'visible') {
            if (videoElement.paused) {
                videoElement.play(); // 播放
            }
        } else {
            if (!videoElement.paused) {
                videoElement.pause(); // 暂停
            }
        }
    }
    
    // 初始检查页面可见性并启动定时器
    checkVisibility();
    setInterval(checkVisibility, 1000); // 每秒检查一次页面可见性
    
  2. 监听视频元素的play事件,视频开始播放的时候visibilityState应为visible,否则暂停播放。

    video.addEventListener('play', function() {
        if (document.visibilityState !== 'visible') {
            videoElement.pause();	// 视频暂停
        }
    });
    
  3. 冻结相关的属性和方法,避免用户重写。

    Object.defineProperty(document, 'visibilityState', {
        configurable: false,
        enumerable: true,
        writable: false,
        value: document.visibilityState
    });
    
    Object.defineProperty(document, 'hidden', {
        configurable: false,
        enumerable: true,
        writable: false,
        value: document.hidden
    });