javascript - 如何知道页面重定向或刷新操作何时被取消,Javascript
问题描述
我目前正在处理向用户显示 onPageNavigating 预加载器的页面,当用户从浏览器取消导航时,我想删除预加载器。
我已经尝试过。
- 创建倒数计时器
- 启动计时器 onPageNavigating
- 如果计时器已过,则假定导航已取消
- 隐藏预加载器
但是这种方法的问题是导航时间可能会根据网络速度而有所不同,这对于我的用例来说是不可行的。
还有哪些其他选项可以实现,或者是否有可用的浏览器 API 来实现?因为我无法通过搜索找到任何相关内容。
编辑:
问题仍然存在的类似用例:例如访问: https ://developer.android.com/guide或其域内的任何链接,并注意页面顶部的水平预加载器。当您从页面开始导航或刷新时,您会看到显示的预加载器,但正如我解释的那样,挑战仍然存在。立即取消刷新,预加载器仍然可见,但并非如此。
编辑2:也许改写问题会有所帮助。
当我说onPageNavigating
我实际上是指Javascript 的 beforeunload事件时,该事件在用户单击链接或启动页面刷新时触发。
window.addEventListener('beforeunload', function (e) {
showPreloader();
// ...At this point the preloader is visible
});
因为beforeunload
活动可以取消。你怎么知道它什么时候被取消,所以预加载器可以被隐藏?
虚构事件:
window.addEventListener('beforeunloadcancelled', function (e) {
hidePreloader();
// ...This is what i am looking for
});
解决方案
TL;博士
现在有一种方法可以beforeunloadcancelled
在仅使用浏览器 API 的浏览器中接收或模拟事件。但是由于浏览器中的错误仍未修复,它不起作用
下面我将描述主要方法,为什么它不起作用以及可能的解决方法。
仅基于浏览器 API 的方法
模拟beforeunloadcancelled
事件的主要思想是获取用户取消下一页导航请求的事件。您无法在主浏览器上下文中控制此类请求,但幸运的是,您可以从服务工作者上下文中进行控制。
如果您的服务工作者订阅了fetch
事件,您将能够处理您的应用程序发出的所有导航请求:
// in Service Worker global scope
self.addEventListener('fetch', evt => {
if (evt.request.mode === 'navigate') {
console.log(`browser is navigating to the ${evt.request.url} resource now`);
}
});
根据规范,每个Request
对象都有属性signal
。此信号表示一个信号对象,允许您与 DOM 请求(例如 Fetch)进行通信,并在需要时通过AbortController对象中止它。
您可以订阅事件以abort
在请求被浏览器或通过 AbortController 中止时得到通知。在该侦听器中,您可以向父窗口发布消息,其中包含导航请求已被取消的信息,这实际上意味着beforeunloadcancelled
事件:
// in Service Worker global scope
self.addEventListener('fetch', evt => {
if (evt.request.mode === 'navigate') {
console.log(`browser is navigating to the ${evt.request.url} resource now`);
evt.request.signal.addEventListener('abort', async () => {
// exit early if we don't have an access to the client (e.g. if it's cross-origin)
if (!evt.clientId) return;
// get the client
const client = await self.clients.get(evt.clientId);
// exit early if we don't get the client (e.g., if it's closed)
if (!client) return;
// send a message to the client
client.postMessage({
msg: 'navigation request has been canceled'
});
})
}
});
不幸的是,这些abort
事件没有反映在 service worker 中。Chromium 或 Firefox 都没有实现将 AbortSignal 正确发送到FetchEvent.request.signal
. 请看一下:
- https://bugs.chromium.org/p/chromium/issues/detail?id=823697
- https://bugzilla.mozilla.org/show_bug.cgi?id=1394102
所以这种方法目前行不通。
您可以在此处找到有关该主题的更多信息:
- Service Worker 的中止控制器
- https://developers.google.com/web/updates/2017/09/abortable-fetch#in_a_service_worker
- https://github.com/w3c/ServiceWorker/issues/1544
- https://github.com/w3c/ServiceWorker/issues/1284
可能的解决方法
检测服务器端取消的请求
如果你可以访问服务器,你可以检测到导航请求在服务器端被取消了。
如果导航请求被中止,只需向客户端发送通知。您可以使用 WebSockets 发送通知。为确保将通知发送到正确的客户端,client Id
可能会使用一些基于唯一 cookie 的信息。
基于定时器的方法
正如您在问题中提到的,一种可能的解决方法是使用某种计时器:
window.addEventListener('beforeunload', () => {
showPreloader();
setTimeout(() => {
hidePreloader(); // we're assuming navigation has been cancelled
}, TIMEOUT);
});
为了使这种方法更精确,您可以TIMEOUT
根据当前页面加载速度选择值(可以在PerformanceNavigationTiming API中找到):
const [initialNavigation] = window.performance.getEntriesByType('navigation');
const TIMEOUT = initialNavigation.duration * TIMEOUT_RATIO;
使用服务人员跟踪导航请求加载持续时间
有一种方法可以通过 service worker 跟踪导航请求的进度。导航请求完成后,您可以向父窗口发送通知。如果在此通知之后没有立即出现导航,则表示导航已被有效取消,并且可以隐藏预加载器:
// in Service Worker global scope
self.addEventListener('fetch', evt => {
if (evt.request.mode === 'navigate') {
console.log(`browser is navigating to the ${evt.request.url} resource now`);
evt.respondWith(fetch(evt.request).then(response => {
sendHidePreloaderNotification();
return response;
}));
}
});
您始终可以结合最后两个选项开发更稳定的方法。
推荐阅读
- firebase - 如何使用 Flutter 在 Firebase 中正确登录和注册?
- indexing - Julia 和 dbscan 聚类:如何从结果结构中提取元素?
- python - python如何实现字符串的索引?
- flutter - 颤动:从其兄弟中截取小部件的屏幕截图
- arrays - 如果它不为零,则从数组中打印元素
- decision-tree - catboost eval_metrics 返回值
- c# - 在 ComboBoxColumn 中获取 ComboBox 的名称
- angular - 使用 BehaviorSubject 从一个组件向另一个组件发送数据
- css - 改变IE输入框的宽度
- json - 如何在 WSO2 Integration Studio ESB 应用程序中传递不记名令牌,以便能够在 WSO2 API Publisher 中调用 API?