JavaScript Browser Events 瀏覽器事件

在網頁開發中,當使用者與瀏覽器互動時會觸發各式各樣的「事件」(Events)。除了常見的點擊 (click) 或鍵盤 (keydown) 事件外,瀏覽器本身也提供了一系列與視窗狀態、頁面可見性及生命週期相關的事件。掌握這些事件,能讓你更精確地控制應用程式的資源消耗、處理數據儲存並提升使用者體驗。

常見的視窗事件 (Window Events)

這些事件通常綁定在 window 物件上,用來監測瀏覽器視窗整體的狀態變化。

事件名稱觸發時機
load網頁所有資源(包含圖片、樣式表等)都載入完成時觸發。
resize瀏覽器視窗大小被改變時觸發。
scroll使用者捲動網頁時觸發。
beforeunload使用者準備離開頁面(分頁關閉、重新整理)前觸發,可用於提示使用者存檔。
unload頁面正在被卸載時觸發(此事件目前已不建議用於儲存數據)。

視窗事件應用範例

頁面載入完成後執行 init

window.addEventListener('load', (event) => {
  console.log('頁面所有資源已完成載入');
  // 執行初始化邏輯
});

監聽視窗大小改變 (resize)

window.addEventListener('resize', () => {
  console.log(`視窗大小改變為: ${window.innerWidth} x ${window.innerHeight}`);
});

離開頁面前的確認提示

window.addEventListener('beforeunload', (event) => {
  // 標準做法是調用 preventDefault 或設定 returnValue
  event.preventDefault();
  event.returnValue = ''; // 現代瀏覽器多數會顯示自定義的對話框
});

Page Visibility API (頁面可見性)

過去開發者很難判斷使用者是否正在看目前的網頁(例如切換到了別的分頁,或將瀏覽器最小化)。Page Visibility API 解決了這個問題,讓我們可以在頁面被隱藏時暫停耗資源的任務。

visibilitychange 事件

當頁面的可見狀態改變時,document 會觸發 visibilitychange 事件。你可以透過 document.visibilityState 來判斷當前狀態:

  • visible: 頁面內容至少部分可見。
  • hidden: 頁面不可見(切換分頁、瀏覽器最小化、或手機螢幕關閉)。

範例:網頁隱藏時暫停影片播放

document.addEventListener('visibilitychange', function () {
  const video = document.getElementById('myVideo');

  if (document.visibilityState === 'hidden') {
    console.log('頁面隱藏,暫停播放');
    video.pause();
  } else {
    console.log('頁面恢復可見,繼續播放');
    video.play();
  }
});
visibilitychange 是目前公認最可靠的「使用者離開頁面」指標,適合用來在背景停止動畫、輪詢 (polling) 或發送最後的分析數據。

Page Lifecycle API (頁面生命週期)

現代瀏覽器為了節省系統資源(記憶體、CPU、電池),會主動「凍結」(Freeze) 或「暫載」(Discard) 長時間待在背景的分頁。Page Lifecycle API 定義了一套標準化的生命週期狀態,讓開發者能安全地處理這些介入行為。

生命週期狀態 (States)

  1. Active: 頁面可見且擁有焦點 (Focus)。
  2. Passive: 頁面可見但沒有焦點(例如使用者正在看另一個視窗,但此網頁仍可見)。
  3. Hidden: 頁面不可見但尚未被凍結。
  4. Frozen: 瀏覽器暫停了頁面的定時器和任務。
  5. Terminated: 使用者主動關閉了分頁或導向新頁面。
  6. Discarded: 瀏覽器為了節省記憶體,完全卸載了頁面內容(但導覽列仍留着分頁,點擊後會重新載入)。

關鍵生命週期事件

  • freeze: 瀏覽器即將凍結頁面時觸發。此時應停止所有計時器並儲存任何未儲存的 UI 狀態。
  • resume: 當凍結的頁面被重新導向(非重新載入)並恢復執行時觸發。
  • pageshow: 頁面被載入時觸發。可以透過 event.persisted 判斷是否來自 BFCache(瀏覽器的前進/後退快取)。
  • pagehide: 使用者導向新頁面時觸發。

瀏覽器支援度與相容性

Page Lifecycle API 的 freezeresume 事件目前主要由 Chromium 系瀏覽器(Chrome, Edge, Opera)完整支援。

  • Chrome/Edge/Opera: 完整支援 freezeresume
  • Safari/Firefox: 目前仍不支援標準的 freezeresume 事件。

相較之下,pageshowpagehide 事件則是所有現代瀏覽器(包含 Safari, Firefox, Chrome, Edge)都完整支援的標準事件。

  • 全瀏覽器支援: pageshowpagehide 自 2015 年起已成為跨瀏覽器的標準,是處理 BFCache(往返快取)最可靠的方式。
  • 優勢: 使用這些事件取代舊有的 loadunload,能讓網頁更順利地進入 BFCache,提升使用者點擊「上一頁/下一頁」時的載入速度。
實務上,如果您需要開發跨瀏覽器相容的應用,應優先監聽 visibilitychange 以及 pageshow / pagehide,而非僅依賴 freeze

推薦的使用模式

瀏覽器建議開發者不要再依賴 unload 事件,因為它在現代移動端瀏覽器和 BFCache 機制下非常不可靠。

// 監測生命週期狀態變化的推薦寫法
const logStateChange = (state) => {
  console.log('當前狀態:', state);
};

// 監聽凍結事件
window.addEventListener('freeze', () => {
  logStateChange('Frozen');
  // 在這裡儲存資料
});

// 監聽恢復事件
window.addEventListener('resume', () => {
  logStateChange('Resumed');
});

// 使用 visibilitychange 作為主要的離開判斷
document.addEventListener('visibilitychange', () => {
  logStateChange(document.visibilityState);
});
盡量避免使用 unload 事件,因為它會阻止瀏覽器的 BFCache 優化,導致使用者點擊「回上頁」時必須重新載入整頁,影響速度。

實務應用:整合範例

下面是一個綜合範例,用於監測頁面從載入、切換分頁到被凍結的完整過程:

// 取得當前狀態的輔助函式
const getState = () => {
  if (document.visibilityState === 'hidden') return 'hidden';
  if (document.hasFocus()) return 'active';
  return 'passive';
};

let currentState = getState();

const handleStateChange = () => {
  const nextState = getState();
  if (nextState !== currentState) {
    console.log(`狀態改變: ${currentState} -> ${nextState}`);
    currentState = nextState;
  }
};

// 綁定一系列相關事件
['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach((type) => {
  window.addEventListener(type, handleStateChange, { capture: true });
});

window.addEventListener(
  'freeze',
  () => {
    console.log('頁面即將被凍結 (Frozen)');
  },
  { capture: true }
);

window.addEventListener(
  'pagehide',
  (event) => {
    if (event.persisted) {
      console.log('頁面進入 BFCache (等同於 Frozen)');
    } else {
      console.log('頁面即將卸載 (Terminated)');
    }
  },
  { capture: true }
);

透過這些事件,你可以讓網頁應用程式在節省資源的同時,仍能保持穩定且快速的響應能力。

現代技術趨勢與議題

隨著 Web 技術的發展,有些傳統事件的處理方式已被更高效的 API 取代。

網路狀態感應 (Online/Offline)

你可以監聽 onlineoffline 事件來判斷使用者的網路連線狀態,對於 PWA 或需要即時同步的應用非常有用。

window.addEventListener('online', () => console.log('網路已恢復連線'));
window.addEventListener('offline', () => console.log('網路已斷開'));

效能優化:捨棄 scroll/resize 轉向 Observer

傳統的 scrollresize 事件在監聽時會頻繁觸發(High-frequency events),容易造成頁面卡頓。現代開發建議使用以下 API:

  • Intersection Observer: 用於判斷元素是否進入視窗範圍(例如圖片懶加載、無限捲動),效能遠優於監聽 scroll
  • Resize Observer: 用於監聽特定 DOM 元素(而非整個視窗)的大小變化,效能優於 window.resize

BFCache (往返快取) 的影響

現代瀏覽器為了實現「瞬間返回」,會將頁面完整狀態保留在記憶體中(即 BFCache)。這會導致當使用者點擊「回上頁」時,不會觸發 load 事件

  • 解決方案: 使用 pageshow 事件,並檢查 event.persisted 是否為 true 來判斷頁面是否從快取中恢復。

相關閱讀: