确定要离开吗?——浏览器 beforeunload 事件
在视频中,我稍微解释了一下这个beforeunload
事件——它可以让你提示或警告用户他们即将离开你的页面。如果误用,这可能会让你的用户感到沮丧——你为什么要使用它呢?💁♂️ℹ️
✅ 您的用户正在填写表单,例如购买
✅ 正在进行网络 POST,例如保存偏好设置
✅ 您的用户正在撰写博客文章或评论,但它将丢失
🤷 视频或音乐会停止播放
⛔ 您的用户尚未读完文章
⛔ 电子邮件客户端中有一封未读邮件
⛔ 限时优惠!立即购买!🙄💸
重要提示
在我们开始讨论代码之前,我视频里的“tl;dr”是什么意思?📺👨🏫
- 使用该
beforeunload
事件警告用户他们将要关闭你的页面,但仅在重要时使用 - 对象可以用于
Set
控制Promise
beforeunload
- ...并且,也许您可以使用
sendBeacon
而不是提示!
如果您想了解更多信息,请继续阅读!⬇️📖
卸载基础知识
如果您想提示或警告用户他们将要关闭您的页面,您需要添加设置.returnValue
事件的代码beforeunload
:
window.addEventListener('beforeunload', (event) => {
event.returnValue = `Are you sure you want to leave?`;
});
有两件事需要记住。
-
大多数现代浏览器(Chrome 51+、Safari 9.1+ 等)都会忽略您的输入,只显示一条通用消息。这可以防止网页作者编写令人震惊的消息,例如“关闭此标签页会导致您的计算机爆炸!💣”。
-
显示提示并非必然。就像在网页上播放音频一样,如果用户未与您的页面进行交互,浏览器可能会忽略您的请求。想象一下,作为用户,您打开并关闭了一个从未切换到的标签页——后台标签页不应该能够提示您它正在关闭。
可选显示
您可以添加一个简单的条件,通过检查事件处理程序中的某些内容来控制是否提示用户。这是一个相当基本的良好做法,如果您只是想提醒用户他们尚未填写完一个静态表单,那么这种方法可能很有效。例如:
let formChanged = false;
myForm.addEventListener('change', () => formChanged = true);
window.addEventListener('beforeunload', (event) => {
if (formChanged) {
event.returnValue = 'You have unfinished changes!';
}
});
但是,如果你的网页或 Web 应用相当复杂,这些检查可能会变得难以处理。当然,你可以添加更多检查,但一个好的抽象层可以帮到你,并带来其他好处——我稍后会讲到。👷♀️
承诺
因此,让我们围绕Promise
对象构建一个抽象层,它代表工作的未来结果——就像来自网络的响应一样fetch()
。
人们学习 Promise 的传统方式是将其视为单个操作,可能需要几个步骤——从服务器获取数据、更新 DOM、保存到数据库。然而,通过共享Promise
,其他代码可以利用它来监控 Promise 何时完成。
待处理工作
以下是跟踪待处理工作的示例。通过调用addToPendingWork
(Promise
例如,从中返回的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);
}
现在,你需要做的就是调用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);
}
当添加新内容时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));
然后,当提交表单时(可能通过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);
});
瞧!如果用户已经在表单中输入了内容,我们就可以像之前一样使用待处理工作语句来阻止页面卸载。当然,你的忙碌旋转器可能不应该显示“正在保存!”。
发送信标
我已经讲了很多关于待处理工作的内容,以及如何监听 Promise 的完成情况fetch()
。但是,正如我在视频中提到的,你可能并不总是需要提示用户。
如果您发出的网络请求没有有用的结果——您只是将其发送到服务器,并且不关心结果——您可以使用现代浏览器调用navigator.sendBeacon()
。它实际上没有返回值,因此您无法等待其结果(无论成功还是失败)。但是,它明确设计为即使在页面关闭后也能运行。
window.addEventListener('beforeunload', () => {
const data = 'page-closed';
navigator.sendBeacon('/analytics', data);
});
当然,您不必sendBeacon
只使用 - 您可以在页面关闭之前beforeunload
使用它,然后您可能根本不需要实现处理程序,因为您没有要等待的待处理事项!beforeunload
Promise
填充工具
如果您的浏览器不支持sendBeacon
,则它几乎等同于通过 发送 POST 请求fetch()
。您可以使用如下代码进行回退:
if (!navigator.sendBeacon) {
navigator.sendBeacon = (url, data) =>
window.fetch(url, {method: 'POST', body: data, credentials: 'include'}).
}
⚠️ 如果您尝试在 中发出网络请求,则值得这样做,因为即使规范不保证这beforeunload
一点,某些浏览器仍会成功。fetch()
表情符号示例
我用它navigator.sendBeacon()
来记录你在Emojityper上选择表情符号的时间,并生成“热门”📈 列表和表情符号热度🔥。它很适合在那里使用,因为我不需要等待回复,即使你关闭页面,请求也可以发出。😂👍
我希望您喜欢《标准》的这一集以及稍长的解释!
如有疑问,请在下方留言,或在Twitter上联系我。我也很期待听到你的建议或改进建议。🕵️
文章来源:https://dev.to/chromiumdev/sure-you-want-to-leavebrowser-beforeunload-event-4eg5