确定要离开吗?——浏览器 beforeunload 事件

2025-05-25

确定要离开吗?——浏览器 beforeunload 事件

在视频中,我稍微解释了一下这个beforeunload事件——它可以让你提示或警告用户他们即将离开你的页面。如果误用,这可能会让你的用户感到沮丧——你为什么要使用它呢?💁‍♂️ℹ️

✅ 您的用户正在填写表单,例如购买
✅ 正在进行网络 POST,例如保存偏好设置
✅ 您的用户正在撰写博客文章或评论,但它将丢失
🤷 视频或音乐会停止播放
⛔ 您的用户尚未读完文章
⛔ 电子邮件客户端中有一封未读邮件
⛔ 限时优惠!立即购买!🙄💸

重要提示

在我们开始讨论代码之前,我视频里的“tl;dr”是什么意思?📺👨‍🏫

  • 使用该beforeunload事件警告用户他们将要关闭你的页面,但仅在重要时使用
  • 对象可以用于Set控制Promisebeforeunload
  • ...并且,也许您可​​以使用sendBeacon而不是提示!

如果您想了解更多信息,请继续阅读!⬇️📖


卸载基础知识

如果您想提示或警告用户他们将要关闭您的页面,您需要添加设置.returnValue事件的代码beforeunload

window.addEventListener('beforeunload', (event) => {
  event.returnValue = `Are you sure you want to leave?`;
});
Enter fullscreen mode Exit fullscreen mode

有两件事需要记住。

  1. 大多数现代浏览器(Chrome 51+、Safari 9.1+ 等)都会忽略您的输入,只显示一条通用消息。这可以防止网页作者编写令人震惊的消息,例如“关闭此标签页会导致您的计算机爆炸!💣”。

  2. 显示提示并非必然。就像在网页上播放音频一样,如果用户未与您的页面进行交互,浏览器可能会忽略您的请求。想象一下,作为用户,您打开并关闭了一个从未切换到的标签页——后台标签页应该能够提示您它正在关闭。

可选显示

您可以添加一个简单的条件,通过检查事件处理程序中的某些内容来控制是否提示用户。这是一个相当基本的良好做法,如果您只是想提醒用户他们尚未填写完一个静态表单,那么这种方法可能很有效。例如:

let formChanged = false;
myForm.addEventListener('change', () => formChanged = true);
window.addEventListener('beforeunload', (event) => {
  if (formChanged) {
    event.returnValue = 'You have unfinished changes!';
  }
});
Enter fullscreen mode Exit fullscreen mode

但是,如果你的网页或 Web 应用相当复杂,这些检查可能会变得难以处理。当然,你可以添加更多检查,但一个好的抽象层可以帮到你,并带来其他好处——我稍后会讲到。👷‍♀️


承诺

因此,让我们围绕Promise对象构建一个抽象层,它代表工作的未来结果——就像来自网络的响应一样fetch()

人们学习 Promise 的传统方式是将其视为单个操作,可能需要几个步骤——从服务器获取数据、更新 DOM、保存到数据库。然而,通过共享Promise,其他代码可以利用它来监控 Promise 何时完成。

待处理工作

以下是跟踪待处理工作的示例。通过调用addToPendingWorkPromise例如,从中返回的fetch())我们可以控制是否警告用户即将卸载您的页面。

const pendingOps = new Set();

window.addEventListener('beforeunload', (event) => {
  if (pendingOps.size) {
    event.returnValue = 'There is pending work. Sure you want to leave?';
  }
});

function addToPendingWork(promise) {
  pendingOps.add(promise);
  const cleanup = () => pendingOps.delete(promise);
  promise.then(cleanup).catch(cleanup);
}
Enter fullscreen mode Exit fullscreen mode

现在,你需要做的就是调用addToPendingWork(p)一个 Promise,也许是从 返回的fetch()。这对于网络操作之类的操作非常有效——它们自然会返回一个,Promise因为你被网页无法控制的某些东西阻塞了。

忙碌的旋转者

正如我在上面的视频📺🔝中提到的,我们也可以使用待处理任务集来控制忙碌的旋转器。这是一个非常简单的addToPendingWork函数扩展:

function addToPendingWork(promise) {
  busyspinner.hidden = false;
  pendingOps.add(promise);

  const cleanup = () => {
    pendingOps.delete(promise);
    busyspinner.hidden = (pendingOps.size === 0);
  };
  promise.then(cleanup).catch(cleanup);
}
Enter fullscreen mode Exit fullscreen mode

当添加新内容时Promise,我们会显示微调器(通过将其.hidden属性设置为false)。当任何 Promise 完成时,我们会检测是否没有其他工作,如果pendingOps为空,则隐藏微调器。

简单的复选框和忙碌的旋转器

我不是 UX 设计师,所以如何设计一个视觉上吸引人的旋转按钮,就留给读者自己去完成了!👩‍🎨

待处理表格

但是对于上面的例子——一个待处理的表单,该怎么办呢?这里有两个选择。您可以添加第二个 beforeunload处理程序,就像本文开头提到的那样:一个简单的布尔检查。

但如果你对使用这种机制感兴趣Promise,哪怕只是在表单中,我们也可以承诺用户填写表单的概念。这个想法包含两个部分。

首先,我们创建自己的Promise,并在用户开始输入某些内容时将其添加到待处理的工作中:

// create a Promise and send it when the user starts typing
let resolvePendingFormPromise;
const pendingFormPromise =
    new Promise((resolve) => resolvePendingFormPromise = resolve);

// when the user types in the form, add the promise to pending work
myForm.addEventListener('change', () => addToPendingWork(pendingFormPromise));
Enter fullscreen mode Exit fullscreen mode

然后,当提交表单时(可能通过fetch()),我们可以使用网络操作的结果来“解决”原始承诺:

myForm.addEventListener('submit', (event) => {
  event.preventDefault();  // submitting via fetch()

  const p = window.fetch('/submit', ...).then((r) => r.json());
  p.then((out) => { /* update the page with JSON output */ });

  // resolve our "pending work" when the fetch() is done
  resolvePendingFormPromise(p);
});
Enter fullscreen mode Exit fullscreen mode

如果用户已经在表单中输入了内容,我们就可以像之前一样使用待处理工作语句来阻止页面卸载。当然,你的忙碌旋转器可能不应该显示“正在保存!”。


发送信标

我已经讲了很多关于待处理工作的内容,以及如何监听 Promise 的完成情况fetch()。但是,正如我在视频中提到的,你可能并不总是需要提示用户。

如果您发出的网络请求没有有用的结果——您只是将其发送到服务器,并且不关心结果——您可以使用现代浏览器调用navigator.sendBeacon()。它实际上没有返回值,因此您无法等待其结果(无论成功还是失败)。但是,它明确设计为即使在页面关闭后也能运行。

window.addEventListener('beforeunload', () => {
  const data = 'page-closed';
  navigator.sendBeacon('/analytics', data);
});
Enter fullscreen mode Exit fullscreen mode

当然,您不必sendBeacon只使用 - 您可以在页面关闭之前beforeunload使用它,然后您可能根本不需要实现处理程序,因为您没有要等待的待处理事项!beforeunloadPromise

填充工具

如果您的浏览器不支持sendBeacon,则它几乎等同于通过 发送 POST 请求fetch()。您可以使用如下代码进行回退:

if (!navigator.sendBeacon) {
  navigator.sendBeacon = (url, data) =>
      window.fetch(url, {method: 'POST', body: data, credentials: 'include'}).
}
Enter fullscreen mode Exit fullscreen mode

⚠️ 如果您尝试在 中发出网络请求,则值得这样做,因为即使规范不保证这beforeunload一点,某些浏览器仍会成功。fetch()

表情符号示例

我用它navigator.sendBeacon()来记录你在Emojityper上选择表情符号的时间,并生成“热门”📈 列表和表情符号热度🔥。它很适合在那里使用,因为我不需要等待回复,即使你关闭页面,请求也可以发出。😂👍


我希望您喜欢《标准》的这一集以及稍长的解释!

如有疑问,请在下方留言,或在Twitter上联系我。我也很期待听到你的建议或改进建议。🕵️

文章来源:https://dev.to/chromiumdev/sure-you-want-to-leavebrowser-beforeunload-event-4eg5
PREV
Node.js 20.6.0:告别“dotenv”
NEXT
编码概念 - 泛型 什么是泛型?为什么要使用泛型? 附加阅读