使用 Notifications API

通过 Notifications API,网页或应用程序可以发送在页面外部、系统级别显示的通知;这允许 Web 应用向用户发送信息,即使应用程序处于空闲或后台状态。本文将介绍在您自己的应用中使用此 API 的基础知识。

通常,系统通知是指操作系统标准的通知机制:例如,想象一下典型的桌面系统或移动设备如何广播通知。

Desktop notification: To do list via mdn.github.io HEY! Your task "Go shopping" is now overdue

当然,系统通知系统会因平台和浏览器而异,但这没关系,Notifications API 的设计足够通用,可以与大多数系统通知系统兼容。

示例

Web 通知最明显的用例之一是基于 Web 的邮件或 IRC 应用程序,这些应用程序需要在收到新消息时通知用户,即使用户正在使用其他应用程序处理其他事务。现在已经有很多此类示例,例如 Slack

我们编写了一个实际的示例——一个待办事项列表应用程序——以更清晰地展示 Web 通知的使用方式。它使用 IndexedDB 在本地存储数据,并通过系统通知通知用户任务到期。您可以 下载待办事项列表代码,或 查看实时运行的应用程序

请求权限

在应用发送通知之前,用户必须授予应用程序相应的权限。当 API 尝试与 Web 页面之外的某些内容交互时,这是一个常见要求——用户至少需要明确授予该应用程序显示通知的权限一次,从而让用户控制允许哪些应用/网站显示通知。

由于过去滥用推送通知的现象,Web 浏览器和开发者开始实施策略来帮助缓解这个问题。您应该仅在响应用户操作(例如,点击按钮)时请求显示通知的同意。这不仅是最佳实践——您不应该用未经用户同意的通知骚扰用户——而且未来浏览器将明确禁止未响应用户操作而触发的通知权限请求。例如,Firefox 从 72 版本开始就已经这样做,Safari 也是如此。

此外,在 Chrome 和 Firefox 中,除非网站是安全上下文(即 HTTPS),否则您根本无法请求通知,并且您不能再允许从跨域 <iframe> 请求通知权限。

注意:本文中的示例使用 Notification() 构造函数来创建通知。这对于桌面来说是可以的,但在大多数移动浏览器上会抛出 TypeError。如果您针对的是移动设备,则应注册一个服务工作线程并使用 ServiceWorkerRegistration.showNotification() 来代替。

检查当前权限状态

您可以通过检查只读属性 Notification.permission 的值来查看是否已获得权限。它可能有以下三种可能的值之一:

default

用户尚未被询问权限,因此不会显示通知。

granted

用户已被询问过,并且已授予显示通知的权限。

denied

用户已明确拒绝显示通知的权限。

获取权限

如果尚未获得显示通知的权限,应用程序需要使用 Notification.requestPermission() 方法向用户请求。最简单的形式是,我们包含以下内容:

js
Notification.requestPermission().then((result) => {
  console.log(result);
});

这使用的是该方法的基于 Promise 的版本。如果您想支持旧版本,可能需要使用旧的回调版本,它看起来像这样:

js
Notification.requestPermission((result) => {
  console.log(result);
});

回调版本可选地接受一个回调函数,该函数在用户响应显示权限请求后被调用。

注意:没有可靠的方法来检测 Notification.requestPermission 是否支持基于 Promise 的版本。如果您需要支持旧浏览器,只需使用基于回调的版本——尽管它已弃用,但在新浏览器中仍然可用。有关更多信息,请查看 浏览器兼容性表格

示例

在我们的待办事项列表演示中,我们包含了一个“启用通知”按钮,当按下该按钮时,它会为应用程序请求通知权限。

html
<button id="enable">Enable notifications</button>

点击此按钮将调用 askNotificationPermission() 函数:

js
function askNotificationPermission() {
  // Check if the browser supports notifications
  if (!("Notification" in window)) {
    console.log("This browser does not support notifications.");
    return;
  }
  Notification.requestPermission().then((permission) => {
    // set the button to shown or hidden, depending on what the user answers
    notificationBtn.style.display = permission === "granted" ? "none" : "block";
  });
}

首先看第二个主要块,您会看到我们首先检查通知是否受支持。如果受支持,我们运行 Notification.requestPermission() 的基于 Promise 的版本;如果不支持,我们在控制台记录一条消息。

在传递给 then 的 Promise 解析处理程序内部,我们根据用户在权限对话框中的选择来显示或隐藏按钮。如果权限已获得,我们不想显示它,但如果用户选择拒绝权限,我们希望给他们以后改变主意的机会。

创建通知

创建通知很简单;只需使用 Notification 构造函数即可。此构造函数需要一个要在通知中显示的标题以及一些用于增强通知的选项,例如 icon 或文本 body

例如,在待办事项列表示例中,我们在需要时使用以下代码片段创建通知(位于 createNotification() 函数中):

js
const img = "/to-do-notifications/img/icon-128.png";
const text = `HEY! Your task "${title}" is now overdue.`;
const notification = new Notification("To do list", { body: text, icon: img });

关闭通知

使用 close() 来移除对用户不再相关的通知(例如,在消息应用程序中,用户已在网页上阅读了通知;或者在音乐应用程序中,下一首歌曲已在播放,以通知歌曲更改)。大多数现代浏览器会在几秒钟后自动关闭通知(大约四秒),但这通常不是您应该关心的事情,因为它取决于用户和用户代理。关闭也可能发生在操作系统级别,并且用户应该对此保持控制。旧版本的 Chrome 没有自动删除通知,因此您可以只为那些旧版本使用 setTimeout(),以避免删除其他浏览器上的通知托盘中的通知。

js
const n = new Notification("My Great Song");
document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "visible") {
    // The tab has become visible so clear the now-stale Notification.
    n.close();
  }
});

注意:此 API 不应仅用于在固定延迟后(在现代浏览器中)关闭屏幕上的通知,因为此方法还将从任何通知托盘中删除通知,阻止用户在初始显示后与其交互。

注意:当您收到“close”事件时,无法保证是用户关闭了通知。这符合规范,其中规定:“当通知被关闭时,无论是由底层通知平台还是由用户关闭,都必须运行其关闭步骤。”

通知事件

Notification 实例上会触发四个事件:

click

当用户点击通知时触发。

close

通知关闭后触发。

error

如果通知出现问题时触发;这通常是因为由于某种原因通知无法显示。

show

通知显示给用户时触发。

可以使用 onclickoncloseonerroronshow 处理程序来跟踪这些事件。由于 Notification 也继承自 EventTarget,因此可以在其上使用 addEventListener() 方法。

替换现有通知

用户在短时间内收到大量通知通常是不受欢迎的——例如,如果一个消息应用程序为每条传入消息通知用户,而它们发送了很多怎么办?为了避免向用户发送过多通知,可以修改待处理的通知队列,用新通知替换一个或多个待处理的通知。

为此,可以为任何新通知添加一个标签。如果通知已具有相同的标签且尚未显示,则新通知将替换该前一个通知。如果具有相同标签的通知已显示,则前一个通知将被关闭,然后显示新通知。

标签示例

假设以下基本 HTML:

html
<button id="notify">Notify me!</button>
<section id="demo-logs"></section>

通过这种方式可以处理多个通知:

js
const demoLogs = document.querySelector("#demo-logs");

const button = document.querySelector("#notify");

button.addEventListener("click", () => {
  if (Notification?.permission === "granted") {
    demoLogs.innerText += `The site has permission to show notifications. Showing notifications.\n`;
    // If the user agreed to get notified
    // Let's try to send ten notifications
    let i = 0;
    // Using an interval cause some browsers (including Firefox) are blocking notifications if there are too much in a certain time.
    const interval = setInterval(() => {
      // Thanks to the tag, we should only see the "Hi no 9 from MDN." notification
      const n = new Notification(`Hi no ${i} from MDN.`, {
        tag: "soManyNotification",
      });
      if (i === 9) {
        clearInterval(interval);
      }
      i++;
    }, 200);
  } else if (Notification?.permission !== "denied") {
    demoLogs.innerText += "Requesting notification permission.\n";
    // If the user hasn't told if they want to be notified or not
    // Note: because of Chrome, we are not sure the permission property
    // is set, therefore it's unsafe to check for the "default" value.
    Notification.requestPermission().then((status) => {
      // If the user said okay
      if (status === "granted") {
        demoLogs.innerText +=
          "User granted the permission. Sending notifications.\n";
        let i = 0;
        // Using an interval cause some browsers (including Firefox) are blocking notifications if there are too much in a certain time.
        const interval = setInterval(() => {
          // Thanks to the tag, we should only see the "Message no 9 from MDN." notification
          const n = new Notification(`Message no ${i} from MDN.`, {
            tag: "soManyNotification",
          });
          if (i === 9) {
            clearInterval(interval);
          }
          i++;
        }, 200);
      } else {
        // Otherwise, we can fallback to a regular modal alert
        demoLogs.innerText += `User denied the permission request.\n`;
      }
    });
  } else {
    // If the user refuses to get notified, we can fallback to a regular modal alert
    demoLogs.innerText += `The site does not have permission to show notifications.\n`;
  }
});

结果

要测试上述示例,请更改 https://live.mdnplay.dev 网站的 发送通知设置

另见