visibilitychange 事件?visibilitychange 是瀏覽器提供的原生事件,屬於 Page Visibility API(頁面可見性 API)的核心部分。當頁面的可見性狀態發生變化時(如頁面從 “顯示” 變為 “隱藏”,或從 “隱藏” 變為 “顯示”),瀏覽器會觸發該事件,當然如果是uniapp或者是wx也會提供類似apphide appshow這些API。
document.hidden(狀態判斷核心)true 表示頁面當前不可見(后台),false 表示頁面可見(前台)。document.visibilityState(更詳細的狀態描述)visible:頁面可見(至少部分可見,如瀏覽器視窗未最小化,標籤頁激活)。hidden:頁面不可見(如切換到其他標籤頁、瀏覽器最小化、手機切后台)。prerender:頁面正在預渲染(用戶未實際看到,通常用於優化加載,部分瀏覽器支持)。unloaded:頁面即將被卸載(如關閉標籤頁,部分瀏覽器支持)。hidden 和 visible 是最常用的兩個狀態。visibilitychange 事件document.visibilityState 或 document.hidden 變化時觸發,需通過 document.addEventListener 監聽。// 監聽 visibilitychange 事件
document.addEventListener('visibilitychange', handleVisibilityChange);
// 事件處理函數
function handleVisibilityChange() {
// 方法1:用 document.hidden 判斷
if (document.hidden) {
console.log('頁面進入后台(不可見)');
// 執行后台邏輯:暫停視頻、清除定時器、保存數據等
pauseVideo();
clearInterval(timer);
saveUserState();
} else {
console.log('頁面回到前台(可見)');
// 執行前台邏輯:恢復視頻播放、重啟定時器、刷新數據等
playVideo();
restartTimer();
refreshData();
}
// 方法2:用 document.visibilityState 判斷(更細致)
switch (document.visibilityState) {
case 'visible':
console.log('頁面可見(前台)');
break;
case 'hidden':
console.log('頁面隱藏(后台)');
break;
case 'prerender':
console.log('頁面預渲染中');
break;
case 'unloaded':
console.log('頁面即將卸載');
break;
}
}
當頁面不需要監聽狀態變化時(如元件銷毀),需移除事件監聽:
// 頁面卸載前解除監聽
window.addEventListener('beforeunload', function() {
document.removeEventListener('visibilitychange', handleVisibilityChange);
});
// 或在 Vue/React 元件銷毀時(以 Vue 為例)
export default {
beforeDestroy() {
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
}
};
頁面切后台時暫停視頻 / 音訊,切回前台時恢復播放:
const video = document.getElementById('myVideo');
function handleVisibilityChange() {
if (document.hidden) {
video.pause(); // 切后台暫停
} else {
video.play(); // 切前台恢復
}
}
頁面在后台時,暫停輪詢接口(如實時聊天、數據刷新),避免浪費資源:
let pollTimer;
function startPoll() {
pollTimer = setInterval(() => {
fetch('/api/refresh'); // 輪詢接口
}, 5000);
}
function stopPoll() {
clearInterval(pollTimer);
}
function handleVisibilityChange() {
if (document.hidden) {
stopPoll(); // 后台停止輪詢
} else {
startPoll(); // 前台重啟輪詢
}
}
切后台時記錄 “離線時間”,切前台時更新 “在線狀態”:
let lastLeaveTime;
function handleVisibilityChange() {
if (document.hidden) {
lastLeaveTime = new Date().getTime(); // 記錄切后台時間
reportUserState('offline'); // 上報離線狀態
} else {
const onlineTime = new Date().getTime() - lastLeaveTime;
console.log(`用戶離線時長:${onlineTime}ms`);
reportUserState('online'); // 上報在線狀態
}
}
切后台時自動保存表單草稿,避免用戶忘記提交:
function saveFormDraft() {
const formData = {
username: document.getElementById('username').value,
content: document.getElementById('content').value
};
localStorage.setItem('formDraft', JSON.stringify(formData));
}
function handleVisibilityChange() {
if (document.hidden) {
saveFormDraft(); // 切后台時保存草稿
}
}
msVisibilityChange 和 msHidden(實際開發中可忽略,IE 市場佔比極低)。pagehide/pageshow 的區別visibilitychange:僅關注頁面 “可見性”(是否在前台),不關心頁面是否卸載(如切換標籤頁時觸發,但頁面未關閉)。pagehide:頁面即將被卸載時觸發(如關閉標籤頁、跳轉頁面),但切后台時也可能觸發,場景更寬泛,判斷精度低於 visibilitychange。visibilitychange,判斷 “頁面關閉” 可用 pagehide。visibilitychange,無需依賴微信 JS-SDK,可直接使用。visibilitychange(從 visible → hidden → visible),需根據業務判斷是否忽略這種場景。visibilitychange 中可能影響性能,建議僅處理必要操作(如暫停 / 恢復、保存數據)。visibilitychange(document.hidden = true);解鎖後恢復,觸發 visible。visibilitychange 事件是 H5 頁面監聽 “前台 / 後台切換” 的最優方案,具有以下優勢:
document.hidden 或 visibilityState 即可判斷狀態,代碼邏輯清晰。是的,你沒看錯,先前講到的只是常規情況下用戶手動切換后台的觸發,但是在一些場景下還是要慎用
visibilitychange 只能判斷頁面 “可見” 或 “隱藏”,但無法區分隱藏的具體場景,例如:
hidden),分享完成後切回前台(觸發 visible),但用戶實際並未離開頁面,可能干擾業務邏輯(如暫停的視頻被誤觸發播放 / 暫停)。visibilitychange 事件在頁面恢復時延遲觸發,或部分邏輯(如定時器)無法正常執行。visibilitychange 的 unloaded 狀態在多數瀏覽器中支持不佳,且頁面真正關閉時(如用戶點擊關閉標籤頁),visibilitychange 可能與 beforeunload、pagehide 事件順序混亂,難以可靠判斷 “用戶是否徹底離開”。例如,用戶關閉標籤頁時,hidden 會先變為 true,但此時頁面即將卸載,後續邏輯(如上報數據)可能因頁面關閉而中斷。
document.visibilityState = 'visible' 表示頁面 “至少部分可見”(如瀏覽器窗口只露出一小塊),但無法判斷頁面是否 “完全可見”(如被其他窗口遮擋了大部分)。如果業務需要精確判斷 “用戶是否正在全屏瀏覽”,visibilitychange 無法滿足,需結合 document.fullscreenElement 等 API 輔助判斷。
用戶是 “切換到其他 App” 還是 “切換到瀏覽器的其他標籤頁”?
是 “手機鎖屏” 還是 “瀏覽器窗口最小化”
是 “分享到微信好友後暫時離開” 還是 “徹底關閉頁面”?
window.blur)且可見性變為隱藏(document.hidden = true),但瀏覽器進程仍在運行,setTimeout 等定時器可能繼續執行(取決於瀏覽器優化策略)。document.hidden = true),還可能伴隨瀏覽器進程被 “冷凍”(尤其是移動端),定時器執行會延遲或暫停。focus/blur 事件與定時器延遲檢測(一般情況可用,所以慎用):
let isTabSwitch = false;
let timer;// 監聽可見性變化
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// 頁面隱藏時啟動定時器,檢測延遲
timer = setTimeout(() => {
// 若定時器延遲超過100ms,大概率是切換到其他App(瀏覽器被冷凍)
console.log('可能切換到其他App');
}, 100);
} else {
clearTimeout(timer);
// 頁面恢復時,若之前觸發了blur且無明顯延遲,可能是切換標籤頁
if (isTabSwitch) {
console.log('可能切換到瀏覽器其他標籤頁');
isTabSwitch = false;
}
}
});
// 監聽焦點變化
window.addEventListener('blur', () => {
if (document.hidden) {
isTabSwitch = true; // 隱藏時失去焦點,可能是切換標籤頁
}
});
- 原理:切換標籤頁時,瀏覽器仍在前台運行,定時器延遲較小;切換到其他 App 時,瀏覽器進入后台,定時器可能被延遲執行。
## 二、區分 “手機鎖屏” vs “瀏覽器窗口最小化”
### 核心思路:利用 “鎖屏” 的特殊性 —— 通常伴隨設備螢幕關閉,而 “窗口最小化” 僅窗口不可見
- 手機鎖屏時:螢幕完全關閉,瀏覽器可能觸發 `visibilitychange` 且後續操作(如觸摸事件)完全失效。
- 瀏覽器窗口最小化(PC 端):螢幕仍亮,只是窗口不可見。
### 輔助判斷方法:
1. **結合 `screen` 對象的亮度或喚醒狀態(移動端有限支持)**:
```javascript
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// 檢測螢幕是否變暗(部分設備支持)
if (typeof screen.brightness !== 'undefined' && screen.brightness < 0.1) {
console.log('可能是手機鎖屏');
} else {
console.log('可能是瀏覽器窗口最小化');
}
}
});
screen.brightness 相容性較差(主要支持安卓部分瀏覽器),iOS 基本不支持。touchstart 事件是否失效(移動端):鎖屏後,頁面無法接收觸摸事件,可在頁面恢復可見時檢測是否有 “鎖屏期間的觸摸記錄”(無記錄則可能是鎖屏):
let hasTouchDuringHidden = false;
document.addEventListener('touchstart', () => {
if (document.hidden) {
hasTouchDuringHidden = true;
}
});document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
if (!hasTouchDuringHidden) {
console.log('可能是手機鎖屏(期間無觸摸)');
} else {
console.log('可能是窗口最小化(期間可能有觸摸其他窗口)');
hasTouchDuringHidden = false;
}
}
});
## 三、區分 “分享到微信好友後暫時離開” vs “徹底關閉頁面”
### 核心思路:利用 “分享” 操作的前置行為(如點擊分享按鈕)和頁面生命週期差異
- 分享到微信好友後暫時離開:用戶點擊分享按鈕 → 頁面隱藏 → 分享完成後用戶可能返回,頁面會再次觸發 `visible`。
- 徹底關閉頁面:頁面隱藏後,會接著觸發 `pagehide` 或 `beforeunload` 事件,且不會再恢復 `visible`。
### 輔助判斷方法:
1. **監聽微信分享按鈕的點擊事件**(微信 H5 場景):在微信環境中,用戶分享前通常會點擊自定義的 “分享按鈕”,可透過該行為標記 “可能是分享導致的離開”:
```javascript
let isSharing = false;
// 假設分享按鈕id為shareBtn
document.getElementById('shareBtn').addEventListener('click', () => {
isSharing = true; // 標記用戶觸發了分享操作
});
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
if (isSharing) {
console.log('可能是分享到微信好友後暫時離開');
// 30秒後若未恢復可見,視為可能關閉頁面
setTimeout(() => {
if (document.hidden) {
console.log('分享後未返回,可能已關閉頁面');
isSharing = false;
}
}, 30000);
} else {
console.log('可能是其他原因關閉頁面');
}
} else {
if (isSharing) {
console.log('分享後返回頁面');
isSharing = false;
}
}
});
pagehide 事件判斷頁面是否卸載:徹底關閉頁面時,pagehide 事件會在 visibilitychange 之後觸發,可透過該事件確認 “頁面已關閉”:
let isPageClosed = false;
window.addEventListener('pagehide', () => {
isPageClosed = true;
console.log('頁面已關閉');
});document.addEventListener('visibilitychange', () => {
if (document.hidden && !isPageClosed) {
console.log('頁面隱藏但未關閉(可能是分享後暫時離開)');
}
});
### 總結:
**沒有絕對的完美,只有不斷地完善,當后台切換時還是要儘可能的與原有應用進行事件關聯才會更準確的判斷用戶的操作行為。**
---
原文出處:https://juejin.cn/post/7564681385294921737