前端 H5 中的 visibilitychange
事件是一个非常重要的事件,它允许开发者检测页面(文档)的可见性状态何时发生变化。这对于优化性能、改善用户体验以及进行精确的分析统计都非常有帮助。
visibilitychange
事件的触发条件
visibilitychange
事件在文档的可见性状态发生改变时触发。document.visibilityState
属性会反映当前的可见性状态,它有以下几个可能的值:
-
visible
: 页面内容至少部分可见。 -
hidden
: 页面内容不可见。这可能发生在以下几种情况:- 用户切换到了另一个浏览器标签页或窗口。
- 用户最小化了浏览器窗口。
- 用户切换到了另一个应用程序(浏览器窗口失去焦点)。
- 移动设备上用户锁屏或切换应用。
- 移动设备上浏览器进入后台。
-
prerender
: 页面正在被预渲染(例如,通过<link rel="prerender" href="...">
)。在这种状态下,页面内容是不可见的,但浏览器正在后台加载和处理它。当用户实际导航到这个预渲染的页面时,状态会从prerender
变为visible
。 -
unloaded
: 页面正在被卸载。这个状态不被所有浏览器支持,并且通常在pagehide
或beforeunload
事件之后发生。
总结触发情况:
- 标签页切换: 从当前标签页切换到其他标签页,或从其他标签页切换回当前标签页。
- 窗口最小化/最大化: 最小化浏览器窗口,或将最小化的窗口恢复。
- 应用切换: 用户从浏览器切换到其他应用程序,或从其他应用程序切换回浏览器。
- 移动设备锁屏/解锁: 在移动设备上,用户锁屏或解锁屏幕。
- 预渲染页面激活: 页面从
prerender
状态变为visible
。
visibilitychange
事件的常见用途场景
由于 visibilitychange
事件能够准确反映用户是否正在查看页面,它在许多场景下都非常有用:
-
资源优化和性能管理:
-
暂停/恢复媒体播放: 当页面变为
hidden
时,暂停视频和音频播放,以节省带宽和设备电量。当页面变为visible
时,恢复播放。- 场景示例: 视频网站在用户切换标签页时自动暂停视频。
-
停止/启动动画和耗时计算: 当页面不可见时,停止不必要的 JavaScript 动画、WebGL 渲染、Canvas 绘图或复杂的后台计算,减少 CPU 和 GPU 占用。
- 场景示例: 游戏在用户离开时自动暂停。
-
控制网络请求: 暂停或延迟不必要的 AJAX 轮询、WebSocket 连接活动或数据同步,直到页面再次可见。
- 场景示例: 聊天应用在用户切换到其他标签页时暂停拉取新消息,返回后立即刷新。
-
-
用户体验 (UX) 提升:
-
显示通知: 当页面不可见时,如果收到新的消息或有重要更新,可以通过浏览器通知(Notification API)提醒用户。当用户返回页面时,可以清除通知。
- 场景示例: 实时聊天应用在收到新消息且页面隐藏时弹出桌面通知。
-
游戏暂停: 确保游戏在用户切换到其他应用或标签页时自动暂停,避免用户错过关键操作或游戏状态被意外改变。
-
数据刷新: 当用户从其他标签页或应用返回时,自动刷新页面内容,确保用户看到的是最新数据。
- 场景示例: 股票行情页面在用户返回时自动更新最新股价。
-
-
数据统计和分析:
-
精确统计页面停留时间: 传统的页面停留时间可能只计算页面打开到关闭的时间,但
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
事件在资源优化方面的一个典型应用。