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>&copy; 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/
作者
csorz
发布于
2020年12月15日
许可协议