让网站离线运行 - 什么是 Service Worker 以及如何在网站上自定义“应用安装”按钮。目录:为什么我们需要 Service Worker?Service Worker 如何工作?如何实现 Service Worker 3。实现 Service Worker 自定义“添加到主屏幕”横幅的完整示例。实用链接:

2025-05-26

使网站离线工作 - 什么是服务工作者以及如何在网站上获取自定义应用安装按钮。

目录

为什么我们需要 Service Worker?

Service Worker 如何工作?

如何实现 Service Worker

3. Service Worker 实现完整示例

自定义“添加到主屏幕”横幅。

有用的链接:

嘿大家,

这篇跟我平常的帖子有点不一样(终于有一篇没有 #showdev 标签的帖子了😂)。在这篇文章里,我会解释什么是 Service Worker,如何用原生 JavaScript 实现它们,以及如何在网站上获取自定义的“添加到主屏幕”横幅。

目录

为什么我们需要 Service Worker?

你用过 Instagram(原生应用)吗?主屏幕上会显示图片吗?如果断网了怎么办?它会崩溃吗?或者像 Chrome 那样显示恐龙游戏?嗯,不会……

Instagram 会显示已加载的旧帖子。虽然你无法刷新动态或点赞图片,但能够看到旧帖子还是挺酷的,对吧?

Service Worker 可让您在 Web 上实现类似的功能。您可以避免 Chrome 的过时,而是显示自定义的离线页面!或者,您可以在用户离线浏览时显示网站的一部分(或者,考虑到网站规模较小,可以显示整个网站)。

以下是当您离线时 Twitter 会显示的内容:
Twitter 离线页面图片

事实上,DEV.to 拥有史上最酷的离线页面之一!他们甚至把涂色画布也当成了离线页面!超酷吧?它看起来是这样的:
DEV.to 离线页面图片

这里有一个谦虚的自夸:
去年我制作了一款名为《Edge of The Matrix》的游戏,该游戏可以离线运行!由于游戏规模不大,我能够缓存网站的大部分内容并使整个游戏可以离线运行。
eotm.ml 的离线和在线比较

如你所见,左图是在线时的图像,右图是离线时的图像。离线时游戏画面相同(字体不同)。

它们是怎么工作的?哈哈……saurabh……显然是魔法。

好吧,他们使用 Service Workers🎉 通过上面的三个例子,我想让大家了解网站如何以各种方式使用 Service Workers。

那么,你们都兴奋地想要了解它们的工作原理和实现方法吗?Lezgooo!!!

Service Worker 如何工作?

注意:这是对 Service Worker 工作原理的一个非常表面的解释,如果您在阅读本文后有兴趣了解更多信息,我在文章末尾链接了一些参考资料

Service Worker 可以访问缓存、来自应用程序的请求以及互联网。

由于您可以访问这三项内容,因此您可以按照自己想要的方式编写代码并处理请求。

该图解释了服务工作者如何从应用程序接收请求,并根据需求将请求重定向到缓存或互联网

Service Worker 可以监听用户的请求,

以下是我们通常希望使用已注册的服务工作者来加载请求的方式:

  1. 用户点击您的网站 URL(并因此请求您的/index.html
  2. 服务工作者有一个 fetch 事件监听器来监听这个事件。
  3. 现在,由于 Service Worker 可以访问caches控制缓存的对象,它可以检查/index.html缓存中是否存在该对象。
  4. 如果 index.html缓存中存在:则使用index.html缓存中的文件进行响应。否则:将请求转发到互联网,并返回互联网的响应。

服务工作者生命周期。

当您首次注册服务工作者时,它会进入install状态,安装服务工作者后它会进入active

现在,假设您更新了服务工作线程,在这种情况下,新的服务工作线程将进入install然后waiting状态,而旧的服务工作线程仍然在控制之中并且是active

关闭标签页并打开网站的新实例后,来自该waiting州的服务人员将接管控制并继续active

Google Developers 网站对 Service Worker Lifecycle 进行了详细的解释,我建议你去看看:https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle

如何实现 Service Worker

在我们开始讨论代码之前,您应该了解以下几点:

  1. 首先,您需要告诉您的网站您的服务工作者文件在哪里。(即注册服务工作者)。
  2. Service Worker 文件无法访问 DOM。如果您之前使用过 Web Worker,那么 Service Worker 也是一种 JavaScript Worker。
  3. 您可以postMessage从 Service Worker 文件来回切换,这允许您与 Service Worker 进行对话。

1. 服务人员注册。

在您的index.html(或任何源自 .html 的 .js 文件中)

<html>
<body>
<!-- Your HTML -->

<script>
// ServiceWorker Registration
if('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
        navigator.serviceWorker.register('serviceworker.js')
            .then(registration => {
                // Registration was successful
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
            })
            .catch(err => {
                // registration failed :(
                console.log('ServiceWorker registration failed: ', err);
            });
    });
}
</script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

这会将该文件注册/serviceworker.js为 Service Worker。现在,所有 Service Worker 处理代码都将写入该/serviceworker.js文件。

2. 使用 Service Worker 处理请求

2a. 将 URL 存储到缓存中。

耶🎉,Service Worker 已经注册成功!现在我们要把必要的文件添加到缓存中,这样以后即使​​没有网络连接也能加载它们。

serviceworker.js

const CACHE_NAME = "version-1";
const urlsToCache = [
    'index.html',
    'assets/logo-192.png',
    'assets/coverblur.jpg',
    'index.js'
];

// Install the service worker and open the cache and add files mentioned in array to cache
self.addEventListener('install', function(event) {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(function(cache) {
                console.log('Opened cache');
                return cache.addAll(urlsToCache);
            })
    );
});

Enter fullscreen mode Exit fullscreen mode

caches.open(CACHE_NAME)打开与传递给它的名称匹配的缓存(在我们的例子中为“version-1”)。
cache.addAll(urlToCache)将所有 URL 添加到缓存中。

现在我们的缓存中已经有了需要离线加载的所有文件。

2b.从缓存加载文件。

serviceworker.js

// Listens to request from application.
self.addEventListener('fetch', function(event) {
    event.respondWith(
        caches.match(event.request)
            .then(function(response) {
                if (response) {
                    // The requested file exists in the cache so we return it from the cache.
                    return response;
                }

                // The requested file is not present in cache so we send it forward to the internet
                return fetch(event.request);
            }
        )
    );
});

Enter fullscreen mode Exit fullscreen mode

caches.match(event.request)检查 CacheStorage 中是否找到了与 event.request 匹配的内容,如果找到了,则以承诺的方式做出响应。

如果文件不在缓存中,它将返回一个假值,因此您可以进一步发送请求以通过互联网获取数据。

(如果您想加载离线页面,您可以检查获取是否抛出错误。如果获取调用失败,您可以在 .catch() 块中使用 进行响应offline.html。我在下面提到了一个加载离线页面的示例。)

2c.处理新的缓存版本(可选 - 如果你讨厌你的生活,你可以避免这一点)。

哇!你走了好远。或许可以在这里喝点水。

“但是等等,Saurabh,我已经知道如何添加到缓存并从缓存中响应,而且我的代码运行良好,所以这篇文章不是应该在这里结束吗?”

嗯,是的,但实际上不是。

问题来了:
现在你修改了代码,或者添加了一个新的 JavaScript 文件。你希望这些修改反映在你的应用中,但……你的应用仍然显示旧文件……为什么?因为 Service Worker 的缓存里有旧文件。现在你想删除旧缓存,并添加新的缓存。

现在,我们需要做的是,我们需要告诉服务人员删除除刚刚添加的新缓存之外的所有缓存。

我们给缓存起了个键/名称,对吧?“version-1”。现在,如果我们要加载新的缓存,就把这个名称改成“version-2”,并且删除名为“version-1”的缓存。

这就是你要做的事情。

serviceworker.js


self.addEventListener('activate', function(event) {
    var cacheWhitelist = []; // add cache names which you do not want to delete
    cacheWhitelist.push(CACHE_NAME);
    event.waitUntil(
        caches.keys().then(function(cacheNames) {
            return Promise.all(
                cacheNames.map(function(cacheName) {
                    if (!cacheWhitelist.includes(cacheName)) {
                        return caches.delete(cacheName);
                    }
                })
            );
        })
    );
});
Enter fullscreen mode Exit fullscreen mode

既然我们已经将 CACHE_NAME 更新为“version-2”,我们将删除所有不属于“version-2”的缓存。

当我们激活新的 Service Worker 时,我们会删除不必要的旧缓存。

3. Service Worker 实现完整示例

3a. 加载index.htmlindex.js离线代码

index.html

<html>
<body>
<!-- Your HTML -->
<script>
// ServiceWorker Registration
if('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
        navigator.serviceWorker.register('serviceworker.js')
            .then((registration) => {
                // Registration was successful
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
            })
            .catch(err => {
                // registration failed :(
                console.log('ServiceWorker registration failed: ', err);
            });
    });
}
</script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

serviceworker.js

const CACHE_NAME = "version-1";
const urlsToCache = [
    'index.html',
    'index.js'
];

// Install the service worker and open the cache and add files mentioned in array to cache
self.addEventListener('install', function(event) {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(function(cache) {
                console.log('Opened cache');
                return cache.addAll(urlsToCache);
            })
    );
});

// Listens to request from application.
self.addEventListener('fetch', function(event) {
    event.respondWith(
        caches.match(event.request)
            .then(function(response) {

                if (response) {
                    console.log(response);
                    // The requested file exists in cache so we return it from cache.
                    return response;
                }

                // The requested file is not present in cache so we send it forward to the internet
                return fetch(event.request);
            }
        )
    );
});


self.addEventListener('activate', function(event) {
    var cacheWhitelist = []; // add cache names which you do not want to delete
    cacheWhitelist.push(CACHE_NAME);
    event.waitUntil(
        caches.keys().then(function(cacheNames) {
            return Promise.all(
                cacheNames.map(function(cacheName) {
                    if (!cacheWhitelist.includes(cacheName)) {
                        return caches.delete(cacheName);
                    }
                })
            );
        })
    );
});

Enter fullscreen mode Exit fullscreen mode

offline.html3b.离线时加载的代码

要加载offline.html实际网站,我们需要将其添加offline.htmlurlsToCache[]数组中。这会将缓存offline.html到 CacheStorage 中。

const urlsToCache = ['offline.html'];
Enter fullscreen mode Exit fullscreen mode

如果您处于离线状态,则获取操作将失败。因此catch,我们可以从该块中返回缓存offline.html

现在用上面示例中的获取监听器块替换此块。

// Listens to request from application.
self.addEventListener('fetch', function(event) {
    event.respondWith(
        caches.match(event.request)
            .then(function(response) {

                // You can remove this line if you don't want to load other files from cache anymore.
                if (response) return response;

                // If fetch fails, we return offline.html from cache.
                return fetch(event.request)
                    .catch(err => {
                        return caches.match('offline.html');
                    })
            }
        )
    );
});
Enter fullscreen mode Exit fullscreen mode

自定义“添加到主屏幕”横幅。

Chrome 中的默认“添加到主屏幕”横幅如下所示:
屏幕截图显示“添加到主屏幕横幅”在 Chrome 中的外观

获取此横幅(根据 Chrome - https://developers.google.com/web/fundamentals/app-install-banners/

  • 该 Web 应用程序尚未安装,并且 prefer_related_applications 不为真。
  • 包含一个Web 应用清单,其中包含:
    • 短名称或名称
    • 图标必须包含 192px 和 512px 大小的图标
    • 起始网址
  • 显示必须是以下之一:全屏、独立或最小用户界面
  • 通过 HTTPS 提供服务(服务人员必需)
  • 已向 fetch 事件处理程序注册了服务工作者

如果您的应用符合这些标准,您网站上的用户将获得如上图所示的“添加到主屏幕”横幅。

但我们甚至可以使用自己的 UI 制作“下载应用程序”按钮,而不是使用默认横幅。

这就是我在我的网络应用程序PocketBook.cc中显示“下载应用程序”按钮的方式:
显示“自定义下载”按钮而不是默认的“添加到主屏幕”的屏幕截图

当 WebApp 可供下载时(即它通过了浏览器设置的标准),它会触发一个名为 的事件beforeinstallprompt

我们可以通过以下方式收听此活动window.addEventListener('beforeinstallprompt', callback)

我们将此事件存储在变量中,以便稍后调用该.prompt()方法。
.prompt()该方法会打开“添加到主屏幕”对话框。因此,当点击“下载应用”按钮时,我们可以调用此方法。

在中index.html,您可以添加

<button class="download-button">Download App</button> <!-- Keep its display:none in css by default. -->
Enter fullscreen mode Exit fullscreen mode

在 JavaScript 中,

let deferredPrompt;
const downloadButton = document.querySelector('.download-button');

window.addEventListener('beforeinstallprompt', (e) => {
    // Stash the event so it can be triggered later.
    deferredPrompt = e;

    // Make the Download App button visible.
    downloadButton.style.display = 'inline-block'; 
});

downloadButton.addEventListener('click', (e) => {
    deferredPrompt.prompt(); // This will display the Add to Homescreen dialog.
    deferredPrompt.userChoice
        .then(choiceResult => {
            if (choiceResult.outcome === 'accepted') {
                console.log('User accepted the A2HS prompt');
            } else {
                console.log('User dismissed the A2HS prompt');
            }
            deferredPrompt = null;
        });
})


Enter fullscreen mode Exit fullscreen mode

有用的链接:


希望这篇文章对您有所帮助。本文是我“让网站离线工作”系列文章的第一部分,下一部分将介绍IndexedDB

感谢您阅读本文🦄请评论您的想法,如果您使用服务工作者来做一些不同且有趣的事情,请在评论部分告诉我!

再见🌻。

文章来源:https://dev.to/saurabhdaware/make-websites-work-offline-part-1-what-are-service-workers-and-how-to-get-a-custom-app-install-button-on-the-website-4a8
PREV
16 种基本问题解决模式
NEXT
我重建了我的投资组合🌻现在它只需 1.6 秒即可加载🎉这是我如何做到的 TLDR 这是我如何提高性能