前端visibilitychange事件

前端 H5 中的 visibilitychange 事件是一个非常重要的事件,它允许开发者检测页面(文档)的可见性状态何时发生变化。这对于优化性能、改善用户体验以及进行精确的分析统计都非常有帮助。

visibilitychange 事件的触发条件

visibilitychange 事件在文档的可见性状态发生改变时触发。document.visibilityState 属性会反映当前的可见性状态,它有以下几个可能的值:

  1. visible: 页面内容至少部分可见。

  2. hidden: 页面内容不可见。这可能发生在以下几种情况:

    • 用户切换到了另一个浏览器标签页或窗口。
    • 用户最小化了浏览器窗口。
    • 用户切换到了另一个应用程序(浏览器窗口失去焦点)。
    • 移动设备上用户锁屏或切换应用。
    • 移动设备上浏览器进入后台。
  3. prerender : 页面正在被预渲染(例如,通过 <link rel="prerender" href="...">)。在这种状态下,页面内容是不可见的,但浏览器正在后台加载和处理它。当用户实际导航到这个预渲染的页面时,状态会从 prerender 变为 visible

  4. unloaded : 页面正在被卸载。这个状态不被所有浏览器支持,并且通常在 pagehidebeforeunload 事件之后发生。

总结触发情况:

  • 标签页切换: 从当前标签页切换到其他标签页,或从其他标签页切换回当前标签页。
  • 窗口最小化/最大化: 最小化浏览器窗口,或将最小化的窗口恢复。
  • 应用切换: 用户从浏览器切换到其他应用程序,或从其他应用程序切换回浏览器。
  • 移动设备锁屏/解锁: 在移动设备上,用户锁屏或解锁屏幕。
  • 预渲染页面激活: 页面从 prerender 状态变为 visible

visibilitychange 事件的常见用途场景

由于 visibilitychange 事件能够准确反映用户是否正在查看页面,它在许多场景下都非常有用:

  1. 资源优化和性能管理:

    • 暂停/恢复媒体播放: 当页面变为 hidden 时,暂停视频和音频播放,以节省带宽和设备电量。当页面变为 visible 时,恢复播放。

      • 场景示例: 视频网站在用户切换标签页时自动暂停视频。
    • 停止/启动动画和耗时计算: 当页面不可见时,停止不必要的 JavaScript 动画、WebGL 渲染、Canvas 绘图或复杂的后台计算,减少 CPU 和 GPU 占用。

      • 场景示例: 游戏在用户离开时自动暂停。
    • 控制网络请求: 暂停或延迟不必要的 AJAX 轮询、WebSocket 连接活动或数据同步,直到页面再次可见。

      • 场景示例: 聊天应用在用户切换到其他标签页时暂停拉取新消息,返回后立即刷新。
  2. 用户体验 (UX) 提升:

    • 显示通知: 当页面不可见时,如果收到新的消息或有重要更新,可以通过浏览器通知(Notification API)提醒用户。当用户返回页面时,可以清除通知。

      • 场景示例: 实时聊天应用在收到新消息且页面隐藏时弹出桌面通知。
    • 游戏暂停: 确保游戏在用户切换到其他应用或标签页时自动暂停,避免用户错过关键操作或游戏状态被意外改变。

    • 数据刷新: 当用户从其他标签页或应用返回时,自动刷新页面内容,确保用户看到的是最新数据。

      • 场景示例: 股票行情页面在用户返回时自动更新最新股价。
  3. 数据统计和分析:

    • 精确统计页面停留时间: 传统的页面停留时间可能只计算页面打开到关闭的时间,但 visibilitychange 允许你更精确地计算用户实际"活跃"在页面上的时间。

    • 用户行为分析: 记录用户何时切换离开页面、何时返回,这有助于分析用户对内容的关注度。

      • 场景示例: 广告平台统计用户实际观看广告的时间。

示例代码

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>VisibilityChange Event Demo</title>
    <style>
        body {
            font-family: sans-serif;
            margin: 20px;
            text-align: center;
        }
        #status {
            margin-top: 20px;
            font-size: 1.5em;
            font-weight: bold;
        }
        #timer {
            margin-top: 10px;
            font-size: 2em;
            color: green;
        }
        .log-container {
            margin-top: 30px;
            border: 1px solid #ccc;
            padding: 15px;
            max-height: 200px;
            overflow-y: auto;
            text-align: left;
        }
        .log-entry {
            background-color: #f9f9f9;
            padding: 5px;
            margin-bottom: 3px;
            border-radius: 3px;
        }
        .hidden-state {
            color: orange;
        }
        .visible-state {
            color: green;
        }
    </style>
</head>
<body>
    <h1>VisibilityChange 事件演示</h1>
    <p>尝试切换标签页、最小化/最大化窗口、或切换到其他应用程序,观察状态和计时器变化。</p>
    <p>当前页面状态: <span id="status"></span></p>
    <p>计时器: <span id="timer">0</span> 秒</p>

    <div class="log-container">
        <h3>事件日志:</h3>
        <div id="eventLog"></div>
    </div>

    <script>
        const statusSpan = document.getElementById('status');
        const timerSpan = document.getElementById('timer');
        const eventLogDiv = document.getElementById('eventLog');
        let seconds = 0;
        let timerInterval;

        function updateStatus() {
            const currentState = document.visibilityState;
            statusSpan.textContent = currentState;
            statusSpan.className = currentState === 'hidden' ? 'hidden-state' : 'visible-state';
            logEvent(`页面状态更新为: ${currentState}`);
        }

        function logEvent(message) {
            const now = new Date();
            const timeString = now.toLocaleTimeString() + '.' + String(now.getMilliseconds()).padStart(3, '0');
            const logEntry = document.createElement('div');
            logEntry.className = 'log-entry';
            logEntry.innerHTML = `<strong>[${timeString}]</strong> ${message}`;
            eventLogDiv.prepend(logEntry); // Add to top
            // 保持日志数量,避免过多
            if (eventLogDiv.children.length > 15) {
                eventLogDiv.removeChild(eventLogDiv.lastChild);
            }
            console.log(`[${timeString}] ${message}`);
        }

        function startTimer() {
            if (!timerInterval) {
                logEvent('计时器开始运行。');
                timerInterval = setInterval(() => {
                    seconds++;
                    timerSpan.textContent = seconds;
                }, 1000);
            }
        }

        function stopTimer() {
            if (timerInterval) {
                logEvent('计时器暂停。');
                clearInterval(timerInterval);
                timerInterval = null;
            }
        }

        // 初始状态
        updateStatus();
        if (document.visibilityState === 'visible') {
            startTimer();
        }

        // 监听 visibilitychange 事件
        document.addEventListener('visibilitychange', () => {
            updateStatus();
            if (document.visibilityState === 'hidden') {
                stopTimer(); // 页面隐藏时暂停计时器
            } else if (document.visibilityState === 'visible') {
                startTimer(); // 页面可见时恢复计时器
            }
            // 对于 'prerender' 或 'unloaded' 状态,通常也视为非可见,可以根据需求处理
        });

        // 辅助监听 window focus/blur,虽然 visibilitychange 更推荐用于文档可见性
        window.addEventListener('focus', () => {
            logEvent('window focus 事件触发。');
        });
        window.addEventListener('blur', () => {
            logEvent('window blur 事件触发。');
        });

    </script>
</body>
</html>

在上述示例中,当页面变为 hidden 时,计时器会暂停;当页面变为 visible 时,计时器会恢复。这展示了 visibilitychange 事件在资源优化方面的一个典型应用。

相关推荐
小小小小宇3 小时前
前端并发控制管理
前端
小小小小宇3 小时前
前端SSE笔记
前端
小小小小宇3 小时前
前端 WebSocket 笔记
前端
小小小小宇5 小时前
前端Loader笔记
前端
烛阴6 小时前
从0到1掌握盒子模型:精准控制网页布局的秘诀
前端·javascript·css
前端工作日常9 小时前
我理解的`npm pack` 和 `npm install <local-path>`
前端
李剑一9 小时前
说个多年老前端都不知道的标签正确玩法——q标签
前端
嘉小华10 小时前
大白话讲解 Android屏幕适配相关概念(dp、px 和 dpi)
前端
姑苏洛言10 小时前
在开发跑腿小程序集成地图时,遇到的坑,MapContext.includePoints(Object object)接口无效在组件中使用无效?
前端