Notification API

深入理解 Notification API 的完整功能:权限请求、通知显示、事件处理、以及 Service Worker 中的后台通知。

Notification API(Notification API)

一、基本用法

1.1 检查支持

if ('Notification' in window) {
  console.log('Notifications supported');
}

1.2 权限级别

// Notification.permission 的值:
// 'granted' - 用户已授权
// 'denied' - 用户已拒绝
// 'default' - 用户尚未决定(将表现为拒绝)

console.log(Notification.permission);

1.3 请求权限

// 同步请求(不推荐)
const permission = Notification.requestPermission();
console.log(permission);  // 'granted' | 'denied' | 'default'

// 异步请求(推荐)
async function requestPermission() {
  const permission = await Notification.requestPermission();
  console.log('Permission:', permission);
  return permission;
}

二、创建通知

2.1 基本通知

if (Notification.permission === 'granted') {
  const notification = new Notification('Hello', {
    body: 'This is a notification',
    icon: '/icon.png'
  });
}

2.2 通知选项

const options = {
  // 通知正文
  body: 'Notification content',

  // 图标
  icon: '/icon.png',

  // 徽标(状态栏图标)
  badge: '/badge.png',

  // 唯一标识(用于分组和去重)
  tag: 'notification-id',

  // 是否在已有 tag 时重新通知
  renotify: true,

  // 是否保持直到用户交互
  requireInteraction: false,

  // 静音
  silent: false,

  // 振动模式
  vibrate: [200, 100, 200],

  // 时间戳
  timestamp: Date.now(),

  // 语言
  lang: 'zh-CN',

  // 文字方向
  dir: 'auto',

  // 自定义数据
  data: { url: '/page' },

  // 操作按钮
  actions: [
    { action: 'accept', title: 'Accept' },
    { action: 'reject', title: 'Reject' }
  ]
};

const notification = new Notification('Title', options);

三、事件处理

3.1 通知事件

const notification = new Notification('Title', options);

// 点击通知
notification.addEventListener('click', (event) => {
  console.log('Notification clicked');
  // 通常打开窗口或聚焦
  window.focus();
  notification.close();
});

// 关闭通知
notification.addEventListener('close', (event) => {
  console.log('Notification closed');
});

// 显示通知
notification.addEventListener('show', (event) => {
  console.log('Notification shown');
});

// 错误
notification.addEventListener('error', (event) => {
  console.error('Notification error');
});

3.2 自动关闭

// 设置自动关闭时间(某些浏览器支持)
const notification = new Notification('Title', {
  body: 'Auto close in 5 seconds',
  requireInteraction: false
});

// 使用 setTimeout 手动关闭
setTimeout(() => {
  notification.close();
}, 5000);

四、Service Worker 中的通知

4.1 发送后台通知

// sw.js
self.addEventListener('push', (event) => {
  const data = event.data ? event.data.json() : {};

  const options = {
    body: data.body || 'New notification',
    icon: data.icon || '/icon.png',
    badge: data.badge || '/badge.png',
    tag: data.tag || 'default',
    data: data.data || {},
    actions: data.actions || [],
    requireInteraction: data.requireInteraction || false
  };

  event.waitUntil(
    self.registration.showNotification(data.title || 'Notification', options)
  );
});

4.2 通知点击处理

// sw.js
self.addEventListener('notificationclick', (event) => {
  event.notification.close();

  const url = event.notification.data?.url || '/';

  event.waitUntil(
    clients.matchAll({ type: 'window', includeUncontrolled: true })
      .then((clientList) => {
        // 如果有匹配的窗口则聚焦
        for (const client of clientList) {
          if (client.url === url && 'focus' in client) {
            return client.focus();
          }
        }
        // 否则打开新窗口
        return clients.openWindow(url);
      })
  );
});

4.3 通知关闭处理

// sw.js
self.addEventListener('notificationclose', (event) => {
  console.log('Notification closed by user');
  // 可以记录分析或清理资源
});

五、权限管理

5.1 Permissions API

// 查询通知权限状态
async function checkPermission() {
  const result = await navigator.permissions.query({ name: 'notifications' });
  console.log('Permission state:', result.state);

  result.addEventListener('change', () => {
    console.log('Permission changed to:', result.state);
  });
}

5.2 权限请求最佳实践

async function ensureNotificationPermission() {
  if (Notification.permission === 'granted') {
    return true;
  }

  if (Notification.permission === 'denied') {
    console.warn('Notifications are blocked');
    // 引导用户到设置页面手动开启
    return false;
  }

  // 请求权限
  const permission = await Notification.requestPermission();
  return permission === 'granted';
}

六、实际应用

6.1 消息通知服务

class NotificationService {
  constructor() {
    this.permission = Notification.permission;
  }

  async init() {
    if (this.permission === 'default') {
      this.permission = await Notification.requestPermission();
    }
    return this.permission === 'granted';
  }

  show(title, options = {}) {
    if (this.permission !== 'granted') {
      console.warn('Notification permission not granted');
      return null;
    }

    return new Notification(title, {
      icon: '/icon.png',
      badge: '/badge.png',
      ...options
    });
  }

  // 快捷方法
  success(message, options = {}) {
    return this.show('Success', {
      body: message,
      tag: 'success',
      ...options
    });
  }

  error(message, options = {}) {
    return this.show('Error', {
      body: message,
      tag: 'error',
      requireInteraction: true,
      ...options
    });
  }

  info(message, options = {}) {
    return this.show('Info', {
      body: message,
      tag: 'info',
      ...options
    });
  }
}

const notifier = new NotificationService();

参考资料

延展阅读