PWA DEMO
PWA 不依赖于单个 API ,而是使用各种技术来实现提供最佳 Web 体验的目标。
PWA 所需的关键要素是 service worker 支持。 值得庆幸的是,桌面和移动设备上的所有主流浏览器都支持 service worker。
其他功能,如 Web App Manifest,Push,Notifications和 Add to Home Screen 功能也得到了广泛的支持。 目前,Safari 对 Web App Manifest 和 Add to Home Screen 的支持有限,并且不支持 Web 推送通知。 但是,其他主流浏览器支持所有这些功能。
Service Worker
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>PWA DEMO</title>
<meta name="description" content="PWA DEMO">
<meta name="author" content="csorz">
<meta name="theme-color" content="#B12A34">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:image" content="icons/icon-512.png">
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="style.css">
<link rel="manifest" href="manifest.json">
<script src="data/list.js" defer></script>
<script src="app.js" defer></script>
<script src="page.js" defer></script>
</head>
<body>
<header>
<p><a class="logo" href="//yhorz.cn"><img src="img/logo.png" alt="pwa"></a></p>
</header>
<main>
<h1>List</h1>
<p class="description">List of games submitted to the <a href="http://js13kgames.com/aframe">A-Frame category</a> in the <a href="http://2017.js13kgames.com">js13kGames 2017</a> competition. You can <a href="https://github.com/mdn/pwa-examples/blob/master/js13kpwa">fork js13kPWA on GitHub</a> to check its source code.</p>
<button id="notifications">Request dummy notifications</button>
<section id="content"></section>
</main>
<footer>
<p>© js13kGames 2012-2018, created and maintained by <a href="http://end3r.com">Andrzej Mazur</a> from <a href="http://enclavegames.com">Enclave Games</a>.</p>
</footer>
</body>
</html>manifest.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51{
"name": "PWA DEMO",
"short_name": "PWA",
"description": "Progressive Web App: PWA DEMO",
"icons": [
{
"src": "icons/icon-32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "icons/icon-64.png",
"sizes": "64x64",
"type": "image/png"
},
{
"src": "icons/icon-96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "icons/icon-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "icons/icon-168.png",
"sizes": "168x168",
"type": "image/png"
},
{
"src": "icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "/demo-pwa/index.html",
"display": "fullscreen",
"theme_color": "#B12A34",
"background_color": "#B12A34"
}app.js 注册 Service Workder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34/* eslint-disable*/
// 注册 Service Workder
// 兼容性:https://www.caniuse.com/?search=serviceWorker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js')
}
// 请求通知授权
// 兼容性:https://www.caniuse.com/?search=Notification
Notification && Notification.requestPermission().then((result) => {
// result: 未操作默认值'default'、同意授权'granted'、拒绝授权'denied'
if (result === 'granted') {
randomNotification()
} else if (result === 'denied') {
// info
} else {
}
})
// 随机发送通知
function randomNotification() {
const randomItem = Math.floor(Math.random() * games.length)
const notifTitle = games[randomItem].name
const notifBody = 'Created by ' + games[randomItem].author + '.'
const notifImg = 'data/img/' + games[randomItem].slug + '.jpg'
const notifOptions = {
body: notifBody,
icon: notifImg
}
const notif = new Notification(notifTitle, notifOptions)
console.log(notif)
setTimeout(randomNotification, 30000)
}sw.js Service Workder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62/* eslint-disable*/
// Service Worker
// 加载数据
self.importScripts('data/list.js')
// 文件缓存清单
const cacheName = `cache-v202012161654`
const appShellFiles = [
'/demo-pwa/',
'/demo-pwa/index.html',
'/demo-pwa/app.js',
'/demo-pwa/page.js',
'/demo-pwa/style.css',
'/demo-pwa/fonts/graduate.eot',
'/demo-pwa/fonts/graduate.ttf',
'/demo-pwa/fonts/graduate.woff',
'/demo-pwa/favicon.ico',
'/demo-pwa/img/logo.png',
'/demo-pwa/img/bg.png',
'/demo-pwa/icons/icon-32.png',
'/demo-pwa/icons/icon-64.png',
'/demo-pwa/icons/icon-96.png',
'/demo-pwa/icons/icon-128.png',
'/demo-pwa/icons/icon-168.png',
'/demo-pwa/icons/icon-192.png',
'/demo-pwa/icons/icon-256.png',
'/demo-pwa/icons/icon-512.png'
]
const gamesImages = []
for (let i = 0; i < games.length; i++) {
gamesImages.push(`data/img/${games[i].slug}.jpg`)
}
const contentToCache = appShellFiles.concat(gamesImages)
// 安装
self.addEventListener('install', (e) => {
console.log('[Service Worker] Install')
e.waitUntil(
caches.open(cacheName).then((cache) => {
console.log('[Service Worker] Caching all: app shell and content')
return cache.addAll(contentToCache)
})
)
})
// 抓取内容
self.addEventListener('fetch', (e) => {
e.respondWith(
caches.match(e.request).then((r) => {
console.log(`[Service Worker] Fetching resource: ${e.request.url}`)
return r || fetch(e.request).then((response) => {
return caches.open(cacheName).then((cache) => {
console.log(`[Service Worker] Caching new resource: ${e.request.url}`)
cache.put(e.request, response.clone())
return response
})
})
})
)
})page.js 页面渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47/* eslint-disable*/
// Generating content based on the template
const template = '<article><img src='data/img/placeholder.png' data-src='data/img/SLUG.jpg' alt='NAME'><h3>#POS. NAME</h3><ul><li><span>Author:</span> <strong>AUTHOR</strong></li><li><span>Twitter:</span> <a href='https://twitter.com/TWITTER'>@TWITTER</a></li><li><span>Website:</span> <a href='http://WEBSITE/'>WEBSITE</a></li><li><span>GitHub:</span> <a href='https://GITHUB'>GITHUB</a></li><li><span>More:</span> <a href='http://js13kgames.com/entries/SLUG'>js13kgames.com/entries/SLUG</a></li>
</ul></article>'
let content = ''
for (let i = 0; i < games.length; i++) {
let entry = template.replace(/POS/g, (i + 1))
.replace(/SLUG/g, games[i].slug)
.replace(/NAME/g, games[i].name)
.replace(/AUTHOR/g, games[i].author)
.replace(/TWITTER/g, games[i].twitter)
.replace(/WEBSITE/g, games[i].website)
.replace(/GITHUB/g, games[i].github)
entry = entry.replace('<a href='http:///'></a>', '-')
content += entry
};
document.getElementById('content').innerHTML = content
// 图片懒加载
const imagesToLoad = document.querySelectorAll('img[data-src]')
function loadImages(image) {
image.setAttribute('src', image.getAttribute('data-src'))
image.onload = () => {
image.removeAttribute('data-src')
}
}
// 说明:https://developer.mozilla.org/zh-CN/docs/Web/API/Intersection_Observer_API
// 兼容性:https://www.caniuse.com/?search=IntersectionObserver
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((items, observer) => {
items.forEach((item) => {
if (item.isIntersecting) {
loadImages(item.target)
observer.unobserve(item.target)
}
})
})
imagesToLoad.forEach((img) => {
observer.observe(img)
})
} else {
imagesToLoad.forEach((img) => {
loadImages(img)
})
}
参考资料
https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps/Introduction
PWA DEMO
http://example.com/20201215-h5-pwa/