ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

切屏检测技术揭秘:从浏览器事件到F12调试与防作弊实践

切屏检测技术揭秘:从浏览器事件到F12调试与防作弊实践 又到期末季打开各种在线考试系统最让考生心头一紧的往往不是题有多难而是页面右上角那行“切屏检测已开启”。作为一名常年跟浏览器打交道的Web前端开发我看到这种提示的第一反应倒不是焦虑而是职业病犯了这功能到底是怎么实现的浏览器端到底能感知到什么程度的“切屏”如果用F12开发者工具去观察能看到哪些运行细节这篇文章我不会去写什么灰色操作而是从技术实现的正当角度把在线考试切屏检测这件事彻底拆开。适合正在做在线教育、考试系统、远程监考类产品的前端开发者也适合那些对浏览器事件机制、JavaScript原生API和jQuery实战感兴趣的同学。我会从事件原理讲到代码实现从jQuery版本适配讲到F12调试技巧最后再把兼容性、误报、上报策略这些实际踩过的坑全部抖出来。先把丑话说在前面技术本身没有对错但用途有。你可以用F12去观察一个正经开发者写的检测逻辑是怎样的但请别拿它去破坏考试规则那属于给自己挖坑。1. 先搞懂一件事切屏检测到底在检测什么1.1 浏览器端能感知的“切屏”事件要理解切屏检测先得明白浏览器在用户离开页面的那一刻会触发哪些事件。很多人以为切屏检测是什么黑科技其实它把控的就是几个最基础的DOM事件。我们用原生JavaScript就能一一看到。当用户从当前标签页切换到其他标签页或者把浏览器窗口最小化、切到其他应用时document.visibilityState会从visible变成hidden同时触发visibilitychange事件。当用户切回来时状态又变为visible。这套机制是Page Lifecycle API的一部分从早期的IE就支持IE 10有document.onvisibilitychangeIE 10以下需要用document.onfocusout这类事件凑合到今天已经是非常稳定的标准。另外一套事件就是window对象上的blur和focus。blur表示窗口失焦focus表示窗口重新获得焦点。这两个事件跟visibilitychange有交集但并不完全等价。比如用户只是点击了一下浏览器菜单栏、弹出的扩展程序面板窗口焦点可能短暂丢失但页面还是可见的反过来用户当前在页面里敲键盘窗口处于聚焦状态但页面可能已经被其他窗口完全遮住了此时visibilityState并不会变成hidden。所以严谨的检测方案一般会把visibilitychange作为主信号blur作为辅助信号两者任一触发都认为发生了“切屏”。我打一个比方浏览器就像一个监控摄像头页面是正在接受考试的“演员”。切走画面的时候摄像头会向后台控制室页面脚本发出“演员离场”的通知切回来的时候又会发出“演员回到画面”的通知。检测系统做的就是把这两条通知的时间和次数通通记下来。1.2 前端检测的边界它能记录什么记录不了什么弄清了事件原理就能明白前端切屏检测的能力边界。它能记录的无非是这么几件事切屏次数、每次切出去的时长、切屏发生的时间点。有些系统还会记录鼠标是否长时间静止、滚动频率是否异常、是否突然粘贴大段文本这些属于行为采集但核心判断还是落在切屏事件上。反过来它记录不了“你用另一台手机搜答案”这种事。浏览器端的JavaScript完全无法感知房间里的其他设备这是物理层面的限制。一个考生如果拿另一台手机查资料切屏检测是看不见的除非考试平台做了手机端的摄像头监控那另说。那为什么各平台还是把切屏检测当成标配其实核心思路是威慑加留痕。对于大部分顺手切出去翻资料的人检测能留下清晰的操作日志后台一旦发现切屏次数多、切出时间长、切出回来之后答题敏捷度提高就有理由判罚。整套方案确实挡不住铁了心用物理外挂的人但已经能筛掉很大一部分临时起意的行为。2. 从0到1用JavaScript实现一个切屏检测模块2.1 最简版本先让浏览器“开口说话”先来一个最直观的版本不搞任何工程化就用原生JavaScript监听visibilitychange事件在控制台打一条日志document.addEventListener(visibilitychange, function () { if (document.visibilityState hidden) { console.log(页面被切走了时间, new Date().toLocaleTimeString()); } else if (document.visibilityState visible) { console.log(页面回来了时间, new Date().toLocaleTimeString()); } });把这段代码粘到任意网页的控制台切到别的标签页再切回来应该能看到两行日志。注意这里判断的是document.visibilityState而不是document.hidden虽然两者表达的意思几乎一样但前者语义更清楚而且也能顺带拿来做后续的状态机流转。如果再补充一个blur事件兜底逻辑可以这样写let isOut false; window.addEventListener(blur, function () { isOut true; console.log(窗口失焦); }); window.addEventListener(focus, function () { isOut false; console.log(窗口重新聚焦); });blur和focus在某些极端浏览器版本里比visibilitychange更早触发可以作为补充信号。但要注意不要重复计数所以一般用布尔标记或者时间戳去重。2.2 工程化升级记录切屏次数、时长与宽限期仅仅打个日志当然不够考试系统的检测模块需要记录可量化的数据。我一般会定义一个SwitchMonitor类负责维护切屏状态、累计次数和切出时长并提供生成上报数据的接口。核心数据结构大概长这样class SwitchMonitor { constructor(options {}) { this.gracePeriod options.gracePeriod || 3000; // 宽限期默认3秒内切回不计入 this.reportCallback options.reportCallback || null; this.records { switchCount: 0, // 有效切屏次数 totalDuration: 0, // 累计切出时长单位毫秒 maxDuration: 0, // 单次最长切出时长 logs: [] // 每次切屏的详细日志 }; this.isHidden false; this.hideTimestamp 0; this._bindEvents(); } _bindEvents() { document.addEventListener(visibilitychange, () { if (document.visibilityState hidden) { this._onHide(); } else if (document.visibilityState visible) { this._onShow(); } }); window.addEventListener(blur, () { if (document.visibilityState hidden) { this._onHide(); } }); window.addEventListener(focus, () { if (document.visibilityState visible) { this._onShow(); } }); } _onHide() { if (this.isHidden) return; this.isHidden true; this.hideTimestamp Date.now(); } _onShow() { if (!this.isHidden) return; this.isHidden false; const duration Date.now() - this.hideTimestamp; if (duration this.gracePeriod) { this.records.switchCount; this.records.totalDuration duration; this.records.maxDuration Math.max(this.records.maxDuration, duration); this.records.logs.push({ hideTime: this.hideTimestamp, showTime: Date.now(), duration }); } } getSummary() { return { ...this.records }; } }这里有一个非常重要的设计细节宽限期。为什么默认取3秒因为很多用户只是误触了一下系统通知或者手滑点到浏览器地址栏这种切出去的时长往往不足1秒如果每次眨眼都记一次切屏误报率会高到后台没法看。我接触过的主流考试平台通常把宽限期设在2秒到5秒之间。太短则误报多太长又容易被钻空子3秒是一个比较稳妥的中间值。同时注意我在blur里面加了document.visibilityState hidden的判断这能避免窗口失焦但页面仍可见时产生重复计数。_onHide和_onShow方法内部也有if (this.isHidden) return;这种防重入保护防止多个事件同时触发导致状态错乱。这些细节都是实际项目里必须考虑到的。2.3 数据上报localStorage暂存与请求发送最麻烦的是上报环节。你可能会想简单啊切屏的时候直接发个AJAX不就行了。但问题在于用户切走标签页时浏览器可能会暂停甚至冻结后台页面的JavaScript执行尤其是在移动端和开启了节能模式的浏览器里请求根本来不及发出去。所以可靠的做法分两步先写入localStorage再统一上报。写入localStorage的好处显而易见本地磁盘写入不依赖于网络即时生效哪怕页面标签页被冻结记录也已经在本地了。等用户切回来、页面恢复执行再把积攒的记录批量发到服务器。如果用户在中途直接关掉页面localStorage里还留着上次的切屏记录下次打开还能补交。上报的方式有三种可选我列一下对比上报方式特点适用场景传统AJAXjQuery的$.ajax或fetch可控性强能自定义header和回调但页面冻结时可能发不出去用户切回页面后的补报navigator.sendBeacon专门用于页面卸载场景浏览器保证尽力发送不影响用户离开页面隐藏或关闭瞬间的数据上报fetch keepalive开关携带keepalive:true的fetch请求和sendBeacon类似但能自定义类型需要更多控制逻辑的场景我常用的做法是组合式上报在检测到切屏发生的瞬间先写localStorage当页面恢复可见时调用sendBeacon把累计数据上报一次同时清空本地缓存。如果是普通的前后端交互页面也可以用jQuery的$.ajax批量上报。代码大致如下const STORAGE_KEY exam_switch_log; function saveToLocal(record) { const list JSON.parse(localStorage.getItem(STORAGE_KEY) || []); list.push(record); localStorage.setItem(STORAGE_KEY, JSON.stringify(list)); } function reportWithBeacon() { const list JSON.parse(localStorage.getItem(STORAGE_KEY) || []); if (list.length 0) return; const blob new Blob([JSON.stringify({ logs: list })], { type: application/json }); navigator.sendBeacon(/api/exam/heartbeat, blob); localStorage.removeItem(STORAGE_KEY); }这个方案解决了一个关键问题页面被冻结时请求断了怎么办。答案就是不依赖“当时”立刻上报而是先落地再找机会补报。考试平台的后端一般都有心跳接口正常状态下每30秒收到一次心跳如果某条心跳里携带了切屏日志后台就能把两次心跳之间的切屏行为挂上去。这样既保证了数据可靠性也不给服务器造成太大压力。3. jQuery版本老系统改造场景下的实战方案3.1 都202x了为什么还要用jQuery看到jQuery我知道有些同学要问现在都Vue、React的天下了谁还用jQuery但实际情况是在线教育、考试系统这类产品生命周期特别长很多核心系统是五六年甚至十年前用jQuery搭的业务逻辑几千行说重构不是一朝一夕的事。现在市面上依然有大量老系统在跑jQuery 3.x这和文化课考试要兼容IE是一个道理你得面对现实。另外jQuery本身在事件绑定、DOM操作、AJAX封装上确实写得比原生JS省心很多。比如$(document).on(visibilitychange, handler)这种写法天然就解决了事件委托和跨浏览器兼容的问题。早期很多团队选择jQuery就是为了少踩浏览器差异的坑。不过我得提醒一句如果你是写新系统我不会推荐你从零引入jQuery用原生JavaScript或者顺手用Vue/React更合理。这篇文章里讲jQuery版本更多是给那些维护老系统的同学一个可以直接抄的作业同时也给想学习jQuery写法的初学者一个真实的实战案例。3.2 用jQuery重写切屏检测模块jQuery版本的核心逻辑和原生JS一样只是把事件绑定换成了jQuery的写法把上报换成了$.ajax。我把完整实现放在下面顺手用$.extend做了参数合并方便在不同项目中直接修改配置项。(function ($) { $.fn.switchMonitor function (options) { const settings $.extend({ gracePeriod: 3000, apiUrl: /api/exam/switch-record, sendInterval: 30000 }, options); const state { hidden: false, hideTimestamp: 0, localLogs: [] }; // 读取本地已有日志 function readLocal() { try { state.localLogs JSON.parse(localStorage.getItem(switch_logs) || []); } catch (e) { state.localLogs []; } } // 写入本地日志 function writeLocal(log) { state.localLogs.push(log); localStorage.setItem(switch_logs, JSON.stringify(state.localLogs)); } // 批量上报 function batchReport() { if (state.localLogs.length 0) return; $.ajax({ url: settings.apiUrl, type: POST, contentType: application/json, data: JSON.stringify({ logs: state.localLogs }), success: function () { state.localLogs []; localStorage.removeItem(switch_logs); } }); } // 事件绑定 $(document).on(visibilitychange, function () { if (document.visibilityState hidden) { state.hidden true; state.hideTimestamp Date.now(); } else if (document.visibilityState visible) { if (!state.hidden) return; state.hidden false; const duration Date.now() - state.hideTimestamp; if (duration settings.gracePeriod) { writeLocal({ hideTime: state.hideTimestamp, showTime: Date.now(), duration: duration }); } } }); // 定时上报 setInterval(batchReport, settings.sendInterval); // 页面关闭前尽量上报一次 $(window).on(beforeunload, function () { if (navigator.sendBeacon) { const blob new Blob([JSON.stringify({ logs: state.localLogs })], { type: application/json }); navigator.sendBeacon(settings.apiUrl, blob); } else { batchReport(); } }); readLocal(); return this; }; })(jQuery); // 调用方式 $(#exam-panel).switchMonitor({ gracePeriod: 3000, apiUrl: /api/exam/switch-record });这个插件化的写法在真实项目中很实用。你不需要改动业务主页面只要在页面底部引入jQuery、引入这个脚本然后一行代码就能把监控挂在考试容器上。而且$.fn.extend的方式在老系统里非常通用其他模块也都是这么写的接手的人不会有认知负担。3.3 原生JS与jQuery混用时的三个坑在实际项目里很难做到纯jQuery或纯原生JS大多情况是老祖宗代码里两套都有。我踩过的坑基本集中在下面三个提醒大家注意。第一个坑是事件重复绑定。比如原生代码已经用了window.addEventListener(blur, handler)后来别人加了个jQuery的$(window).on(blur, handler)两个handler都会执行如果两边都在计数就会出现切一次屏记两次数的情况。解决办法是统一事件绑定入口或者在上报前做logs去重记录hideTime相同的数据只算一条。第二个坑是$符号冲突。老系统里一般不会遇到但如果引入了其他依赖$的库比如某些可视化图表组件$可能就不是jQuery了。解决方式是用jQuery.noConflict()释放$之后全部写成jQuery(function(){})和jQuery(document).on(...)。第三个坑是DOMContentLoaded时机。原生JS写在head里时document还没加载完直接绑定事件没问题但读取DOM节点会拿到null。jQuery的$(function(){})的处理是把代码延迟到DOM就绪后执行两套逻辑混用时要注意原生代码里依赖DOM内容的逻辑是否执行太早。切屏检测模块不依赖DOM内容但如果你在同一个文件里接了别的逻辑就要统一初始化顺序。4. F12开发者工具亲手验证你的检测代码4.1 Console面板手动触发切屏事件写完了代码怎么用F12开发者工具验证很多前端新手对F12的理解还停留在“看报错”上其实调试这种事件类功能Console就是最顺手的试验场。最直接的验证方式是在Console里手动派发事件模拟切屏场景// 模拟页面被切走 Object.defineProperty(document, visibilityState, { value: hidden, configurable: true }); document.dispatchEvent(new Event(visibilitychange)); // 模拟页面回来 Object.defineProperty(document, visibilityState, { value: visible, configurable: true }); document.dispatchEvent(new Event(visibilitychange));注意这里document.visibilityState本身是只读属性直接赋值是无效的得用Object.defineProperty去临时覆盖它。这样手工触发一遍就能在Console看到检测模块的日志输出确定事件链路是通的。如果你只改了真实标签页的可见性也可以直接切到别的标签页再切回来那样更接近真实用户行为但手动派发的好处是能在不离开页面的情况下反复测试边界条件。还有一个小技巧document.hidden这个属性在新版浏览器里和visibilityState是联动的你改了visibilityStatehidden不一定跟着变所以在代码里不要混用两个判断条件统一用visibilityState就没这个问题。4.2 Sources面板断点观察状态变化事件链路通了之后你想确认_onHide里hideTimestamp是否被正确记录可以用Sources面板打断点。打开F12切到Sources标签找到你的JavaScript源文件在_onHide方法内部的行号上点击一下设置一个断点。然后去Console手动派发切屏事件脚本就会停在断点处此时右侧Scope面板能看到当前作用域里的所有变量值。我平时断点调试时喜欢同时观察state.hidden和hideTimestamp两个值的变化。执行下一步之后再派发一次visibilitychange事件让脚本停在_onShow里看看duration计算结果是不是符合预期。这一步能排查出很多逻辑问题比如宽限期判定写反了或者时间戳计算差了一千倍之类。4.3 Network面板看上报请求是否正常发出上报逻辑对不对最终看Network面板。打开F12切到Network标签勾选Fetch/XHR过滤器然后在Console里触发一次完整流程派发visibilitychange切走等几秒再切回接着调用batchReport()。如果上报接口通了Network列表里会出现一条POST请求点开看Payload里面应该包含刚才的日志数据。如果连sendBeacon都用了还要注意Beacon请求在Network里可能显示为ping类型的请求Filter里不要只勾XHR把All选上。我经常看到有人排查半天说请求没发结果是因为Beacon请求被默认过滤器过滤掉了。4.4 Application面板查localStorage记录最后一步检查localStorage到底存了什么东西。切到Application标签左边展开Local Storage找到你的站点域名就能看到switch_logs这条记录。点击展开可以逐个查看之间的日志条目。这个面板在排查上报失败时特别有用如果Network里没有请求但localStorage里有数据说明上报环节出了问题如果localStorage是空的那就要往回到检测环节找原因。4.5 自测用例与预期结果表为了方便照着测我把常用的自测场景整理成了表格大家可以直接拿去做冒烟测试操作步骤预期结果直接切到其他标签页1秒内切回不增加切屏次数切到其他标签页停留5秒再切回增加1次切屏日志里duration约为5000毫秒在地址栏点一下再点回页面可能触发blur但如果页面可见不计次数浏览器最小化5秒后恢复增加1次切屏点击浏览器菜单栏下拉项可能触发blur但visibilityState仍为visible不计次数打开新窗口盖住当前窗口如果visibilityState变为hidden则计一次移动端按Home键退到桌面再回来增加1次切屏这套用例覆盖了主流的“真切屏”类型同时也能验证宽限期的判定是不是符合预期。5. 兼容性、误报排查与常见坑5.1 visibilitychange在不同浏览器的兼容性差异虽然visibilitychange已经是广泛支持的标准但不代表所有浏览器行为一致。先说旧版SafariiOS 14以前对visibilityState的支持不够完善偶尔出现页面切走但事件不触发的情况。解决办法是在window上监听pageshow和pagehide事件兜底恢复状态。再说桌面端的Edge和Chrome近期版本开启“内存节省模式”后后台标签页会被冻得更狠定时器停走、sendBeacon也可能延迟。针对这种情况我建议所有上报数据以服务端收到的最终时间为准前端本地记录的hideTime只做参考。服务端在落库时可以额外记录一个server_time避免客户端时间被改导致判定不了。如果你在Mac上的Safari里测试还必须注意用户手动触发多任务切换时visibilityState可能变成hidden但焦点事件没有同步触发所以检测方案一定不能只依赖focus事件。5.2 输入法、菜单点击、弹窗误报的罪魁祸首误报是这类系统最头疼的问题。用户只是切一下输入法的中英文或者不小心碰了一下电脑通知栏会不会直接被记为切屏这要看你的触发条件。先说输入法。Windows下切换输入法可能会让输入框短暂失焦但页面本身还是可见的所以如果我们只用blur事件做判断误报会很高。这也是我在第二节里强调要加document.visibilityState hidden条件的原因——至少能把“窗口还看得见”的场景全部排除掉。再说浏览器弹窗。比如用户在页面里点了某个按钮弹出了一个浏览器级别的confirm弹窗此时document.visibilityState可能还是visible但window会失去焦点。这种情况下不应该记切屏。这两个事件的关系我建议在路由层做一个合法性判断切屏记录的判据统一以visibilityState hidden为准其他事件只作为辅助参考。5.3 浏览器标签页休眠与定时器问题后台标签页的定时器节流是另一个容易踩的坑。浏览器为了省电会把后台标签页的setInterval频率大幅降低极端情况下甚至暂停。也就是说你精心设置的30秒上报定时器在用户切走之后可能根本不触发。这种情况下页面恢复可见后的一次补报就显得十分重要。为了更稳妥我还会在每次visibilitychange恢复可见时主动对比本地localStorage里最近一条日志的时间和当前时间如果相差超过一个阈值就按缺口的时长补记。注意这不是造数据而是防止浏览器冻结期间用户其实切走了但事件没触发完的情况。实际项目中可以这么处理恢复可见时计算一下时间差如果时间差大于宽限期就把这条补录进日志。5.4 常见问题速查表我把项目过程中遇到的典型问题整理成一张表排查的时候可以直接对着看现象可能原因解决方案切走时没反应visibilitychange没触发或事件重复绑定被覆盖确认浏览器版本在Console手动派发事件验证明明没切屏却被记录blur事件误触发且没校验visibilityState在blur处理里增加hidden状态判断上报请求发不出去后台标签页定时器被节流改用visibilitychange恢复时的补报和sendBeaconlocalStorage一直增长上报接口异常或返回了错误状态检查Network面板的请求状态补一个上报成功后的清理逻辑手机和电脑行为不一致移动端切换App时事件时序不同增加pageshow/pagehide兜底事件切屏时间不准确客户端系统时间被改服务端落库时另存server_time前端时间只做参考5.5 一套建议的告警阈值与判定规则最后分享一套我在项目中常用到的后端判定规则给做服务端的同学一个参考。不是说前端检测到一次切屏就必须判作弊那样太粗暴。一个好的考试系统应该允许用户有少量误操作只有当行为模式明显异常时才触发人工复核。我梳理的规则大致如下指标正常范围异常阈值单场考试切屏次数0~2次超过5次单次切出时长小于10秒单次超过30秒累计切屏时长小于60秒超过3分钟切回后首题作答时间无明显异常3秒以内且答案正确率明显偏高切屏时间段分布均匀零散集中在某几道题尤其是简单客观题前端检测负责提供数据后端判定负责综合决策两者配合才能形成可信的防作弊方案。这套阈值不是硬性标准不同考试场景要灵活调整。比如开卷考试完全可以放宽闭卷考试则要收紧。6. 关于防作弊前端能做和不能做的事6.1 为什么我不打算写“绕过”教程网上搜“F12 绕过切屏检测”能搜出一堆教程但我不会教那些东西原因很简单在线考试是有明确规则的严肃场景用技术手段绕过检测属于破坏规则一旦被平台核实轻则当次成绩作废重则记入诚信档案。为了一次考试冒这种风险完全不值得。而且从技术角度说试图用开发者工具去修改页面变量、删除事件监听器这条路本身就是容易留痕的。现在稍微正规一点的考试系统都会做前端事件监控你打开F12的那一刻console是否被打开、窗口大小是否变化、debugger断点是否被人为触发这些都可以被采集。你以为的隐蔽操作在后台可能早就是一堆红色告警了。6.2 从系统设计者角度哪些异常行为更容易被判定如果你做的是考试系统的开发方你更应该关心后端行为分析怎么做。我见过不少团队把宝全押在前端切屏次数上这是不对的。真正可信的防线是多维度行为分析切屏次数、IP归属变化、设备指纹突变、作答速度是否符合常模、鼠标轨迹是否过于生硬、粘贴内容比例是不是过高。举一个例子某个考生明明只切屏了1次但那一题的作答时间只有0.8秒且答案完全正确同时他的设备指纹从Mac变成了Windows这种综合特征比单纯10次切屏要可疑得多。规则引擎要做的是把这些信号按权重汇总触发人工复核流程。前端采集只是原料后端分析才是决策大脑。6.3 一套可信的防作弊系统前后端配合长什么样在我的实践经验里一套相对完整的在线考试防作弊系统至少包含四层第一层是前置身份核验人脸比对加证件验证第二层是前端行为采集就是我们讲的切屏检测、鼠标轨迹、复制操作第三层是网络与设备信息采集包括IP归属、设备指纹、浏览器指纹第四层是后端数据分析与规则引擎把前三层的数据串起来判断风险等级。这四层缺一不可。只做前端切屏检测拦不住拿手机查答案的人只做IP核验拦不住同一个房间两个人分工合作只做规则引擎又会因为数据不足产生大量误报。真正的防作弊系统是一个综合工程而不是一行visibilitychange监听。6.4 给同样在学JavaScript的朋友一句实在话我知道读这篇文章的人有一部分正在准备期末考试有一部分刚学前端不久。JavaScript是一门能力很强的语言你可以用它写切屏检测插件也可以用它做数据可视化、写自动化工具、搭一个完整的在线教育平台。技能本身没有原罪关键是你把它用在哪个方向。如果你对浏览器API、事件机制这些底层东西感兴趣我建议顺着这几个方向继续深入研究Page Lifecycle API、IntersectionObserver、MutationObserver、CSP策略、Service Worker。把视野放在建设优质产品上能力增长的速度会远超你跑去研究怎么绕过别人检测的速度。最后一点经验之谈我在给一个在线考试平台做前端模块时曾经连续测试了十几个小时的切屏上报最后还是发现移动端Safari在后台冻结后sendBeacon会偶发丢失。后来换成“本地记录加恢复可见时补报”的双保险方案才彻底解决问题。这种踩过坑才换来的经验比单纯记住某个API语法有用得多。如果你要在自己的项目里落地切屏检测我的最后建议是第一宽限期一定要留第二判断条件统一走visibilityState第三上报必须本地缓存加补报第四上线前用F12把表格里的自测用例全部跑一遍。把这些做到了一个稳定、低误报、可追溯的切屏检测模块就基本成型了。至于更复杂的行为分析和判定策略那是另一个值得深度展开的话题以后有机会再写。
返回列表