博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
用PWA来做一个天气应用
阅读量:7251 次
发布时间:2019-06-29

本文共 8145 字,大约阅读时间需要 27 分钟。

什么是PWA

Progressive Web App, 简称 PWA,是提升 Web App 的体验的一种新方法,能给用户原生应用的体验。

上面是Lavas给出的简单描述,PWA给我的个人感觉来说,就是将原生APP的体验搬到浏览器上,包括例如在桌面上生成icon,快速启动,可以离线使用,可以推送消息,总而言之,它需要具备原生APP的所有特点,并在此基础上更进一步。

PWA应用的技术

  • Service Worker
  • cacheStorage
  • Push Notification(本应用并未涉及)

应用演示

这个应用的想法源自于,在学习PWA的时候看到这个demo,不过里面的代码基本看不懂。。。所以就用了它的UI设计和ICON,自己开始慢慢摸索。

(因为没有对PC样式进行适配,所以请在手机端或chrome手机调试模式打开,chrome点击"添加到主屏幕"即可添加桌面ICON)

项目结构

  • images(存放图片)
  • fontSet.js(根据不同手机设置全局字体)
  • index.html(主页面)
  • main.js(主程序)
  • manifest.json(控制桌面启动程序(icon)的添加)
  • reset.css(清空默认样式)
  • skeleton(骨架屏,用于加载时过渡)
  • style.css(主样式)
  • sw.js(service worker进程)

缓存App Shell

首先,我们需要在主进程注册一个service worker

// 注册service workerwindow.addEventListener('DOMContentLoaded', function() {    SW.register();})const SW = {    // 注册    register() {        // 检测serviceWorker是否可用        if('serviceWorker' in navigator) {            navigator.serviceWorker.register('./sw.js')            .then(function() {                console.log('Service Worker Registered');            })            .catch(function() {                console.log('Service Worker failed');            })        }    }}复制代码

如果注册成功了,service worker就正式开始工作了,service worker的生命周期可以简单描述为

serviceWorker(第一次安装或者发生变化) -> install -> activite

所以此时就进入了第一步“install“,在install过程中缓存我们离线时需要的文件,这里包括像页面本身,页面的样式,还有主程序等,但是需要注意的是,千万不能把sw.js也缓存进去,不然你的应用就永远更新不了了

const CACHENAME = 'weather-' + 'v4';const PATH = '/pwaTest';const fileToCache = [    PATH + '/',    PATH + '/index.html',    PATH + '/main.js',    PATH + '/fontSet.js',    PATH + '/skeleton.js',    PATH + '/reset.css',    PATH + '/style.css',    PATH + '/images/icons/delete.svg',    PATH + '/images/icons/plus.svg',    PATH + '/images/partly-cloudy.png',    PATH + '/images/wind.png',    PATH + '/images/cloudy_s_sunny.png',    PATH + '/images/cloudy.png',    PATH + '/images/clear.png',    PATH + '/images/rain.png',    PATH + '/images/fog.png',    PATH + '/images/icons/icon-32x32.png',    PATH + '/images/icons/icon-128x128.png',    PATH + '/images/icons/icon-144x144.png',    PATH + '/images/icons/icon-152x152.png',    PATH + '/images/icons/icon-192x192.png',    PATH + '/images/icons/icon-256x256.png'];self.addEventListener('install', e => {    console.log('Service Worker Install');    e.waitUntil(        caches.open(CACHENAME).then(function (cache) {            self.skipWaiting();            console.log('Service Worker Caching');            return cache.addAll(fileToCache);        })    )})复制代码

注:e.waitUntil()是等待一个Promise对象执行完毕后。

当install完毕后,进入activate进程,我们需要清理掉旧的缓存,不然浏览器还会使用旧缓存,并且旧缓存也占用着空间。

self.addEventListener('activate', function (event) {    event.waitUntil(        // 遍历 caches 里所有缓存的 keys 值        caches.keys().then(function (cacheNames) {            return Promise.all(                cacheNames.map(function (NAME) {                    if (NAME != CACHENAME) {                        // 删除掉除了当前版本之外的缓存文件                        return caches.delete(NAME);                    }                })            );        })    );});复制代码

fetch

最开始的时候我并不知道fetch的作用是什么,只是跟着示例代码敲,程序就能正常运行,我原以为service worker是类似与vue组件间的emit和on一样,通过message来传递数据。

但service worker并不是,简而言之,service worker是通过监听fetch事件来拦截所有的请求,并对其进行处理,这些请求包括服务器对服务器本地文件的请求(index.html,style.css,main.js),也包括了对外部接口的调用(GET,POST请求)

self.addEventListener("fetch", function(e) {    // e是所有的请求,没调用一次请求,都会被fetch监听到    e.respondWith(caches.match(e.request).then(function(response) {          // 在caches中寻找response,如果有就返回response,如果没有,就继续fetch(即不在本地查找,调用接口去查找)          return response || fetch(e.request);    }));});复制代码

至此,这个应用的初步框架已经搭建起来了。

离线功能

我们知道,PWA的一大特点就是可以离线使用,所以我们需要对我们的代码进行一些处理。

self.addEventListener('fetch', e => {    e.respondWith(        caches.match(e.request).then(function (res) {            if (res) {                if (e.request.url.indexOf(self.location.host) !== -1) {                    // 同源                    return res;                } else {                    // 离线状态                    if (!navigator.onLine) {                        return res;                    } else {                        return fetch(e.request).then((response) => {                            let responeClone = response.clone();                            let responeClone_2 = response.clone();                            responeClone_2.json().then(data => {                                caches.open(CACHENAME).then(cache => {                                    cache.put(e.request, responeClone);                                })                            }).catch(e => {                                console.log(e);                            })                            return response;                        })                    }                }            }            // 远程js文件            if (e.request.url.indexOf('https://pv.sohu.com/cityjson?ie=utf-8') !== -1) {                return fetch(e.request);            }            return fetch(e.request).then((response) => {                let responeClone = response.clone();                let responeClone_2 = response.clone();                responeClone_2.json().then(data => {                    caches.open(CACHENAME).then(cache => {                        cache.put(e.request, responeClone);                    })                }).catch(e => {                                    })                return response;            }).catch(e => {                            })        })    )})复制代码

大体思路如下:

  • 无论在线/离线,App shell的部分(即同源)总是可以离线获取的,所以直接return res即可
  • 在线时,对于天气的情况,直接调用远程接口(不使用本地缓存),这样做的原因,是因为天气需要实时更新,每次访问时都应该是最新的天气情况,如果调用一次以后就直接去调用缓存的数据,那天气的情况就永远停留在第一次了
  • 离线时,直接或者已经缓存好的天气情况即可

骨架屏

当用户网络情况不佳时,页面信息的加载需要一些时间,但是如果直接留给用户一个大白屏,用户不知道应用是否还是正常工作,所以需要一个过渡,来缓解用户的焦躁,那我们就需要用到骨架屏了

skeleton.js

const Skeleton = {    Render(key, type, row) {        let rows = (function() {            let temp = '';            for (let i = 0; i < row; i ++) {                temp += '

' } return temp; })(); let model = (function() { let temp = ''; switch (type) { case 'normal': temp = `
${ rows }

` break; case 'title': temp = `

${ rows }

` break; default: break; } return document.createRange().createContextualFragment(temp); })(); return model; }}export default Skeleton复制代码

main.js

import skeleton from './skeleton.js'        // 新建一个新的城市天气实例    buildNewCity(city) {        if (navigator.onLine) {            // 骨架屏先行渲染            let preModel = (function() {                return skeleton.Render(city, 'title', 3);            })();            let container = document.getElementById('container');            container.appendChild(preModel);        }        this.getInfoNow(city);    }        // 在线时才需要骨架屏    if (navigator.onLine) {        let container = document.getElementById(this.name);        setTimeout(() => {            container.classList.remove('preload');            container.innerHTML = "";            container.appendChild(model);            // 为删除键绑定事件            document.getElementById('delete_' + this.name).addEventListener('click', function() {                WEATHERINFO.deleteCity(_this.name);            })        }, 200);    } else {        let card = document.createElement('div');        card.classList = ['card ' + 'mg'];        card.id = _this.name;        card.appendChild(model);        let container = document.getElementById('container');        container.appendChild(card);        // 为删除键绑定事件        document.getElementById('delete_' + this.name).addEventListener('click', function() {            WEATHERINFO.deleteCity(_this.name);        })    }复制代码

当用户添加城市时,先将骨架屏放到页面上,再进行fetch操作,当fetch完成后,再将fetch到的数据给对应的div中。

chrome slow 3G下的测试

这就是这个小应用的几个技术要点,可以观看演示,

如果文章对你有用的话,可以点个star哦

转载于:https://juejin.im/post/5cb039476fb9a0687e390ad1

你可能感兴趣的文章
ELASTIC SEARCH 性能调优
查看>>
Java并发总结(三):中断线程
查看>>
Beer Refrigerator
查看>>
hadoop输入分片计算(Map Task个数的确定)
查看>>
TYVJ P1008 传球游戏
查看>>
MVC基础
查看>>
【BZOJ】 Hash Killer I II III
查看>>
为什么st2 chrome无法显示api中的例子
查看>>
Python 3.6 -win64环境安装PIL模块
查看>>
redis事务需要注意的坑------RedisConnectionFailureException
查看>>
SPOJ 4110 Fast Maximum Flow (最大流模板)
查看>>
ECMAScript面向对象(二)——之创建对象方法总结
查看>>
git实践:对比svn
查看>>
1 管理入门
查看>>
C#递归遍历指定目录下的所有文件(包括子目录下的文件)
查看>>
SpringMVC的工作流程
查看>>
JS比较好用的一些方法搜集
查看>>
React Native导航器之react-navigation使用
查看>>
百度2016笔试题第一题:页面请求失败值
查看>>
实现网站图片瀑布流重点记录
查看>>