使网站离线工作 - 什么是服务工作者以及如何在网站上获取自定义应用安装按钮。
目录
为什么我们需要 Service Worker?
Service Worker 如何工作?
如何实现 Service Worker
3. Service Worker 实现完整示例
自定义“添加到主屏幕”横幅。
有用的链接:
嘿大家,
这篇跟我平常的帖子有点不一样(终于有一篇没有 #showdev 标签的帖子了😂)。在这篇文章里,我会解释什么是 Service Worker,如何用原生 JavaScript 实现它们,以及如何在网站上获取自定义的“添加到主屏幕”横幅。
目录
- 为什么我们需要 Service Worker?
- Service Worker 如何工作?
- 如何实现 Service Worker
- 1. Service Worker 注册
- 2. 使用 Service Worker 处理请求
- 3. 实现 Service Worker 的完整示例(如果您实现 Service Worker 的截止日期是明天,请直接跳到此处。)
- 自定义“添加到主屏幕”横幅(如果您来这里只是为了了解如何在网站上获取自定义应用安装按钮,您可以跳转到这里)
- 有用的链接
为什么我们需要 Service Worker?
你用过 Instagram(原生应用)吗?主屏幕上会显示图片吗?如果断网了怎么办?它会崩溃吗?或者像 Chrome 那样显示恐龙游戏?嗯,不会……
Instagram 会显示已加载的旧帖子。虽然你无法刷新动态或点赞图片,但能够看到旧帖子还是挺酷的,对吧?
Service Worker 可让您在 Web 上实现类似的功能。您可以避免 Chrome 的过时,而是显示自定义的离线页面!或者,您可以在用户离线浏览时显示网站的一部分(或者,考虑到网站规模较小,可以显示整个网站)。
事实上,DEV.to 拥有史上最酷的离线页面之一!他们甚至把涂色画布也当成了离线页面!超酷吧?它看起来是这样的:
这里有一个谦虚的自夸:
去年我制作了一款名为《Edge of The Matrix》的游戏,该游戏可以离线运行!由于游戏规模不大,我能够缓存网站的大部分内容并使整个游戏可以离线运行。
如你所见,左图是在线时的图像,右图是离线时的图像。离线时游戏画面相同(字体不同)。
它们是怎么工作的?哈哈……saurabh……显然是魔法。
好吧,他们使用 Service Workers🎉 通过上面的三个例子,我想让大家了解网站如何以各种方式使用 Service Workers。
那么,你们都兴奋地想要了解它们的工作原理和实现方法吗?Lezgooo!!!
Service Worker 如何工作?
注意:这是对 Service Worker 工作原理的一个非常表面的解释,如果您在阅读本文后有兴趣了解更多信息,我在文章末尾链接了一些参考资料
Service Worker 可以访问缓存、来自应用程序的请求以及互联网。
由于您可以访问这三项内容,因此您可以按照自己想要的方式编写代码并处理请求。
Service Worker 可以监听用户的请求,
以下是我们通常希望使用已注册的服务工作者来加载请求的方式:
- 用户点击您的网站 URL(并因此请求您的
/index.html
) - 服务工作者有一个 fetch 事件监听器来监听这个事件。
- 现在,由于 Service Worker 可以访问
caches
控制缓存的对象,它可以检查/index.html
缓存中是否存在该对象。 - 如果
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
在我们开始讨论代码之前,您应该了解以下几点:
- 首先,您需要告诉您的网站您的服务工作者文件在哪里。(即注册服务工作者)。
- Service Worker 文件无法访问 DOM。如果您之前使用过 Web Worker,那么 Service Worker 也是一种 JavaScript Worker。
- 您可以
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>
这会将该文件注册/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);
})
);
});
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);
}
)
);
});
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);
}
})
);
})
);
});
既然我们已经将 CACHE_NAME 更新为“version-2”,我们将删除所有不属于“version-2”的缓存。
当我们激活新的 Service Worker 时,我们会删除不必要的旧缓存。
3. Service Worker 实现完整示例
3a. 加载index.html
和index.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>
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);
}
})
);
})
);
});
offline.html
3b.离线时加载的代码
要加载offline.html
实际网站,我们需要将其添加offline.html
到urlsToCache[]
数组中。这会将缓存offline.html
到 CacheStorage 中。
const urlsToCache = ['offline.html'];
如果您处于离线状态,则获取操作将失败。因此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');
})
}
)
);
});
自定义“添加到主屏幕”横幅。
获取此横幅(根据 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. -->
在 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;
});
})
有用的链接:
- 例如 DEV 的 Service Worker 文件
- Google 开发者:Service Workers - 简介
- Google 开发者:Service Worker 生命周期
- Google 开发者:应用安装横幅
希望这篇文章对您有所帮助。本文是我“让网站离线工作”系列文章的第一部分,下一部分将介绍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