首页 > 解决方案 > 如何知道页面重定向或刷新操作何时被取消,Javascript

问题描述

我目前正在处理向用户显示 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
    });

标签: javascriptpreloader

解决方案


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. 请看一下:

所以这种方法目前行不通。

您可以在此处找到有关该主题的更多信息:

可能的解决方法

检测服务器端取消的请求

如果你可以访问服务器,你可以检测到导航请求在服务器端被取消了。

如果导航请求被中止,只需向客户端发送通知。您可以使用 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;
        }));
    }
});

您始终可以结合最后两个选项开发更稳定的方法。


推荐阅读