之前的文章介绍的缓存方案是白名单形式,只把需要缓存的内容列举出来进行缓存,这篇文章反向思考使用黑名单模式,黑名单里的不缓存,其他的都缓存,一般不缓存的都是第三方域名下的跨域的资源,也无法缓存。

延迟注册serviceworker(每个页面都要加载):
window.addEventListener('load', function() {
if('serviceWorker' in navigator){
// serviceworker.js 文件要放在网站发布的根目录
navigator.serviceWorker.register('/serviceworker.js').then(function (registration) {
console.log('Service Worker Registered,register script: serviceworker.js.');
}).catch(function (error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
});
// 控制台显示service worker缓存占用情况
if ("storage" in navigator && "estimate" in navigator.storage) {
// 检查浏览器是否支持 Web Storage 估计功能
navigator.storage
.estimate()
.then((estimate) => {
// 转换使用量和配额到 MB
const usageMB = estimate.usage / 1024 / 1024;
const quotaMB = estimate.quota / 1024 / 1024;
// 计算使用比例
const proportion = (estimate.usage / estimate.quota) * 100;
// 打印结果
console.log(
`Using ${usageMB.toFixed(2)} out of ${quotaMB.toFixed(
2
)} MB. The proportion is ${proportion.toFixed(2)}%`
);
})
.catch((error) => {
// 处理可能的错误
console.error("Error estimating storage:", error);
});
} else {
// 如果不支持,可以在这里打印一条消息
console.log("Browser does not support storage estimation.");
}这个和之前的一样没有什么大的变化。
然后是serviceworker.js一定是放在网站发布目录的根目录,保证它的作用域最大化。具体的配置发生了变化:
//serviceworker.js
'use strict';
const version = 'v20180608'; //缓存版本号,修改之后缓存会刷新
const __DEVELOPMENT__ = false; //开发模式
const __DEBUG__ = false; //调试模式
const offlineResources = [ //离线断网的时候提供友好界面
'/',
'/offline.html',
'/offline.svg'
];
const ignoreFetch = [ //这里是需要忽略不缓存的的第三方的域名
/chrome-extension:\/\//, // 建议加上忽略chrome插件的协议
/https?:\/\/ae.bdstatic.com\//,
/https?:\/\/msite.baidu.com\//,
/https?:\/\/s.bdstatic.com\//,
/https?:\/\/timg01.bdimg.com\//,
/https?:\/\/zz.bdstatic.com\//,
/https?:\/\/jspassport.ssl.qhimg.com\//,
/https?:\/\/hm.baidu.com\//,
/https?:\/\/sp0.baidu.com\//,
/https?:\/\/s.360.cn\//,
/https?:\/\/s.ssl.qhres.com\//,
/https?:\/\/www.google-analytics.com\//,
/https?:\/\/www.googletagmanager.com\//,
/.php$/
];
//////////
// Install
//////////
function onInstall(event) {
log('install event in progress.');
event.waitUntil(updateStaticCache());
}
function updateStaticCache() {
return caches
.open(cacheKey('offline'))
.then((cache) => {
return cache.addAll(offlineResources);
})
.then(() => {
log('installation complete!');
});
}
////////
// Fetch
////////
function onFetch(event) {
const request = event.request;
if (shouldAlwaysFetch(request)) {
event.respondWith(networkedOrOffline(request));
return;
}
if (shouldFetchAndCache(request)) {
event.respondWith(networkedOrCached(request));
return;
}
event.respondWith(cachedOrNetworked(request));
}
function networkedOrCached(request) {
return networkedAndCache(request)
.catch(() => { return cachedOrOffline(request) });
}
// Stash response in cache as side-effect of network request
function networkedAndCache(request) {
return fetch(request)
.then((response) => {
var copy = response.clone();
caches.open(cacheKey('resources'))
.then((cache) => {
cache.put(request, copy);
});
log("(network: cache write)", request.method, request.url);
return response;
});
}
function cachedOrNetworked(request) {
return caches.match(request)
.then((response) => {
log(response ? '(cached)' : '(network: cache miss)', request.method, request.url);
return response ||
networkedAndCache(request)
.catch(() => { return offlineResponse(request) });
});
}
function networkedOrOffline(request) {
return fetch(request)
.then((response) => {
log('(network)', request.method, request.url);
return response;
})
.catch(() => {
return offlineResponse(request);
});
}
function cachedOrOffline(request) {
return caches
.match(request)
.then((response) => {
return response || offlineResponse(request);
});
}
function offlineResponse(request) {
log('(offline)', request.method, request.url);
if (request.url.match(/.(jpg|png|gif|svg|jpeg)(?.*)?$/)) {
return caches.match('/offline.svg');
} else {
return caches.match('/offline.html');
}
}
///////////
// Activate
///////////
function onActivate(event) {
log('activate event in progress.');
event.waitUntil(removeOldCache());
}
function removeOldCache() {
return caches
.keys()
.then((keys) => {
return Promise.all( // We return a promise that settles when all outdated caches are deleted.
keys
.filter((key) => {
return !key.startsWith(version); // Filter by keys that don't start with the latest version prefix.
})
.map((key) => {
return caches.delete(key); // Return a promise that's fulfilled when each outdated cache is deleted.
})
);
})
.then(() => {
log('removeOldCache completed.');
});
}
function cacheKey() {
return [version, ...arguments].join(':');
}
function log() {
if (developmentMode()) {
console.log("SW:", ...arguments);
}
}
function shouldAlwaysFetch(request) {
return __DEVELOPMENT__ ||
request.method !== 'GET' ||
ignoreFetch.some(regex => request.url.match(regex));
}
function shouldFetchAndCache(request) {
return ~request.headers.get('Accept').indexOf('text/html');
}
function developmentMode() {
return __DEVELOPMENT__ || __DEBUG__;
}
log("Hello from ServiceWorker land!", version);
self.addEventListener('install', onInstall);
self.addEventListener('fetch', onFetch);
self.addEventListener("activate", onActivate);注意到上面的配置文件里面有提到一个离线文件offline.html,这里给出一个简单的版本:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="theme-color" content="#242628">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>网络异常 | Powered by chaihongjun.me</title>
<style>
:root {
--wrapper-width-percent: 80%;
--wrapper-min-width: 600px;
--wrapper-max-width: 1024px;
--navbar-height: 48px;
--area-gap-size: 16px;
--text-gap-size: 4px;
--line-height: 24px;
--border-radius-size: 4px;
--color-dark-1: #353739;
--color-dark-2: #242628;
--color-dark-3: #111517;
--color-medium: #96999b;
--color-light-1: #aaacaf;
--color-light-2: #d6d7d9;
--color-light-3: #f2f4f6;
--color-orange: #f57c00;
--color-blue: #2196f3;
--color-green: #4caf50;
--color-purple: #4a148c;
--color-shadow: rgba(0, 0, 0, 0.3)
}
* {
-webkit-tap-highlight-color: transparent;
box-sizing: border-box;
outline: none
}
@-ms-viewport {
width: device-width;
}
::selection {
background: var(--color-orange);
color: var(--color-light-3)
}
html,
body {
margin: 0;
padding: 0;
font-size: 16px;
font-weight: 400;
background: var(--color-dark-2)
}
body,
button,
input,
textarea,
select {
border-radius: var(--border-radius-size);
font-family: pingfang sc, helvetica neue, hiragino sans gb, segoe ui, microsoft yahei ui, 微软雅黑, sans-serif
}
button {
background: var(--color-orange);
box-shadow: 1px 2px 8px var(--color-shadow);
border: none;
color: var(--color-light-3);
cursor: pointer;
font-size: 16px;
margin: 1rem 0;
padding: calc(var(--text-gap-size) * 2);
width: 15rem
}
button:hover {
background: #ff9c20
}
button:active {
background: #d55c00
}
:disabled {
background: var(--color-light-1);
cursor: no-drop
}
pre,
pre *,
code,
var {
font-family: source code variable, consolas, menlo, monaco, courier new, monospace !important
}
var,
code {
display: inline-block
}
b,
strong {
font-weight: 600
}
a {
text-decoration: none;
color: var(--color-blue)
}
body {
background: #242628;
color: #d6d7d9;
padding: 0 1rem;
}
</style>
</head>
<body>
<h1>网络异常</h1>
<p>你的网络似乎遭遇了中断,无法从 <code>chaihongjun.me</code> 获取最新的数据。</p>
<p>由于 Service Worker 技术,你之前访问过的页面已被缓存,可以从当前设备上离线访问,但部分资源(图片、样式等)可能无法显示。</p>
<button onclick="window.history.back();">返回上一页</button>
<p>若已确认你的网络环境正常,可以点击下方的按钮或按浏览器刷新按钮,来重载当前页面。</p>
<button onclick="location.reload()">立即重载</button>
</body>
</html>



