跳至主要內容
Skip to content

通知推送 (Notifications API)

在現代 Web 應用中,即時通知是提升使用者黏著度的關鍵功能。無論是新訊息提醒、任務完成通知,還是行事曆活動,Web Notifications API 讓我們能夠跳脫瀏覽器視窗,發送系統層級的通知訊息。

本文將帶你深入了解如何使用原生的 Notification API,並將其封裝成一個優雅的 Vue 3 Composable —— useNotification


一、 Web Notifications API 簡介

Notification API 允許網頁向使用者顯示系統通知。這些通知通常會出現在作業系統的通知中心,即使使用者當前不在該分頁(甚至最小化瀏覽器),也能收到訊息。

1.1 核心流程

使用通知 API 主要分為三個步驟:

  1. 檢查支援度:確認瀏覽器是否支援。
  2. 請求權限:必須獲得使用者明確授權 (granted)。
  3. 發送通知:建立 Notification 實例。

1.2 權限狀態 (Notification.permission)

  • default:使用者尚未做出選擇(瀏覽器行為通常等同於拒絕,但可請求權限)。
  • granted:使用者已允許通知。
  • denied:使用者已封鎖通知。

二、 設計 Composable 介面

我們需要一個能夠處理權限請求、狀態管理與通知發送的介面:

typescript
// types/useNotification.ts
import type { Ref, ComputedRef } from "vue";

export interface NotificationOptions {
  /** 通知內文 */
  body?: string;
  /** 通知圖示 URL */
  icon?: string;
  /** 通知圖片 URL (部分系統支援) */
  image?: string;
  /** 分組標籤 (相同 tag 的通知會被覆蓋而非堆疊) */
  tag?: string;
  /** 是否重新通知 (當覆蓋舊通知時是否再次震動/發聲) */
  renotify?: boolean;
  /** 點擊通知時的回呼函式 */
  onClick?: (e: Event) => void;
}

export interface UseNotificationReturn {
  /** 是否支援 Notification API */
  isSupported: ComputedRef<boolean>;
  /** 當前權限狀態 */
  permission: Ref<NotificationPermission>;
  /** 請求權限 */
  requestPermission: () => Promise<NotificationPermission>;
  /** 發送通知 */
  show: (title: string, options?: NotificationOptions) => Notification | null;
  /** 關閉所有此頁面產生的通知 (選擇性實作) */
  closeAll: () => void;
}

三、 實作 useNotification Composable

3.1 核心實作

typescript
// composables/useNotification.ts
import { ref, computed, onMounted, onUnmounted } from "vue";
import type {
  UseNotificationReturn,
  NotificationOptions,
} from "@/types/useNotification";

export function useNotification(): UseNotificationReturn {
  const isSupported = computed(
    () => typeof window !== "undefined" && "Notification" in window,
  );

  const permission = ref<NotificationPermission>("default");

  // 追蹤活躍的通知實例,以便組件卸載時清理
  const activeNotifications: Notification[] = [];

  // 更新權限狀態
  function updatePermission() {
    if (isSupported.value) {
      permission.value = Notification.permission;
    }
  }

  // 請求權限
  async function requestPermission() {
    if (!isSupported.value) return "denied";

    if (permission.value === "default") {
      const status = await Notification.requestPermission();
      permission.value = status;
      return status;
    }
    return permission.value;
  }

  // 發送通知
  function show(
    title: string,
    options: NotificationOptions = {},
  ): Notification | null {
    if (!isSupported.value) return null;

    // 如果尚未授權,先嘗試請求
    if (permission.value === "default") {
      requestPermission().then((p) => {
        if (p === "granted") show(title, options);
      });
      return null;
    }

    if (permission.value !== "granted") return null;

    // 建立通知
    const notification = new Notification(title, {
      body: options.body,
      icon: options.icon,
      image: options.image,
      tag: options.tag,
      renotify: options.renotify,
    });

    // 綁定點擊事件
    if (options.onClick) {
      notification.onclick = options.onClick;
    }

    // 加入管理列表
    activeNotifications.push(notification);

    // 監聽關閉事件以從列表中移除
    notification.onclose = () => {
      const index = activeNotifications.indexOf(notification);
      if (index > -1) activeNotifications.splice(index, 1);
    };

    return notification;
  }

  function closeAll() {
    activeNotifications.forEach((n) => n.close());
    activeNotifications.length = 0;
  }

  onMounted(() => {
    updatePermission();
  });

  // 組件卸載時自動關閉通知(可選,視需求而定)
  onUnmounted(() => {
    closeAll();
  });

  return {
    isSupported,
    permission,
    requestPermission,
    show,
    closeAll,
  };
}

NOTE

HTTPS 要求:現代瀏覽器(尤其是 Chrome)要求網站必須在安全環境(HTTPS)下才能請求通知權限。Localhost 是例外。


四、 實際應用範例

4.1 基本通知與權限按鈕

最基本的用法:一個按鈕請求權限,另一個按鈕發送通知。

vue
<script setup lang="ts">
import { useNotification } from "@/composables/useNotification";

const { isSupported, permission, requestPermission, show } = useNotification();

function sendTestNotification() {
  show("哈囉!Vue 3", {
    body: "這是一則來自 Composable 的測試通知 🚀",
    icon: "/logo.png",
  });
}
</script>

<template>
  <div v-if="isSupported">
    <p>
      權限狀態: <strong>{{ permission }}</strong>
    </p>

    <button v-if="permission === 'default'" @click="requestPermission">
      🔔 啟用通知
    </button>

    <button v-else-if="permission === 'granted'" @click="sendTestNotification">
      📨 發送測試訊息
    </button>

    <div v-else class="error">❌ 通知權限被拒絕,請至瀏覽器設定開啟。</div>
  </div>
  <div v-else>您的瀏覽器不支援通知功能。</div>
</template>

4.2 倒數計時器結束通知

結合計時器,在倒數結束時發送通知,這是非常實用的場景(例如番茄鐘)。

vue
<script setup lang="ts">
import { ref } from "vue";
import { useNotification } from "@/composables/useNotification";

const { show, permission, requestPermission } = useNotification();
const timeLeft = ref(5);

function startTimer() {
  // 確保先取得權限
  if (permission.value === "default") requestPermission();

  const timer = setInterval(() => {
    timeLeft.value--;
    if (timeLeft.value <= 0) {
      clearInterval(timer);

      // 發送通知
      show("時間到!", {
        body: "您的番茄鐘專注時間已結束,起來動一動吧!",
        icon: "/timer.png",
        tag: "pomodoro-timer", // 確保不會重複堆疊
        renotify: true,
      });

      timeLeft.value = 5; // 重置
    }
  }, 1000);
}
</script>

五、 行動版與 PWA 的注意事項

Web Notifications API 在桌機版瀏覽器(Chrome, Firefox, Safari, Edge)支援度很好。但在行動裝置上,情況較為複雜:

  • Android (Chrome): 完整支援。
  • iOS (Safari): 傳統網頁(在 Safari 中開啟)不支援標準 Notification API。
    • 解決方案:從 iOS 16.4 開始,如果使用者將網站加入主畫面 (Add to Home Screen) 成為 PWA,則支援 Web Push 與 Notifications API。
    • 實作差異:iOS PWA 的通知通常需要配合 Service Worker 與 Push API,單純的前端 new Notification() 可能無法運作。

WARNING

如果你的目標受眾包含大量 iOS 使用者,請務必了解 PWAPush API 的相關知識,單純依賴本篇介紹的 Notification API 在 iOS Safari 上是無效的。


總結

概念說明
Notification用於建立與顯示通知的建構函式
requestPermission非同步方法,回傳 granted, denieddefault
tag用於分組/取代舊通知,避免通知中心被洗版
Service Worker背景推送通知(Push API)的基礎,比單純 Notification 更強大

TIP

最佳體驗設計:不要在使用者一進入網站就跳出權限請求(這很煩人且容易被拒絕)。應該設計一個「開啟通知」的文字或按鈕,當使用者點擊並了解用途後,再呼叫 requestPermission


進階挑戰

  1. 點擊互動:實作點擊通知後 (onClick),自動將瀏覽器視窗聚焦 (window.focus()) 並跳轉到特定頁面。
  2. Service Worker 整合:研究如何使用 Service Worker 發送離線推播通知 (Push Notifications)。
  3. 自訂音效:雖然 Notification API 不直接支援自訂音效,但可以在顯示通知時同時使用 Audio API 播放提示音。

延伸閱讀與資源