H5 PWA技术

1、原生app优缺点

  a、体验好、下载到手机上入口方便

  b、开发成本高(ios和安卓)

  c、软件上线需要审核

  d、版本更新需要将新版本上传到不同的应用商店

  e、使用前需下载


2、web网页优缺点

  a、开发成本低、网站更新时上传最新的资源到服务器即可、手机自带浏览器打开即可

  b、体验比原生app差

  c、入口不便捷

  d、无网无相应,不具备离线能力

  e、无app的消息推送


3、PWA是什么?

PWA是一个新的前端技术,全称:Progressive Web App,这是一个渐进式的网页应用程序。结合了一系列现代web技术,在网页应用中实现和原生应用相近的用户体验。

PWA的三个关键词:

  Reliable(可靠的):当用户从手机屏幕启动时,无需考虑网络状态,可以立刻加载出PWA

  Fast(快速的):加载速度快

  Engaging(可参与的):PWA可以添加在用户的主屏幕上,无需从应用商店里下载,他们通过网络应用程序Manifest file提供类似于APP的使用体验(android上可设置全屏显示,由于Safari支持度的问题ios不可以),可以进行“推送通知”

小小总结:

  a、解决的问题:

      1>可添加至主屏幕

      2>实现离线缓存功能

      3>实现消息推送

  b、优势:几乎瞬间加载,但安全且富有弹性

  c、核心:manifest文件清单、Service Workers


4、Manifest

作用:  

  a、能够将你浏览的网页添加到你的手机屏幕上

​  b、在 Android 上能够全屏启动,不显示地址栏 ( 由于 Iphone 手机的浏览器是 Safari ,所以不支持)

  c、控制屏幕 横屏 / 竖屏 展示

​  d、定义启动画面

​  e、可以设置你的应用启动是从主屏幕启动还是从 URL 启动

​  f、可以设置你添加屏幕上的应用程序图标、名字、图标大小

示例:

index.html

  1. <head>
  2. <title>Minimal PWA</title>
  3. <meta name="viewport" content="width=device-width, user-scalable=no" />
  4. <link rel="manifest" href="manifest.json" />
  5. <link rel="stylesheet" type="text/css" href="main.css">
  6. <link rel="icon" href="/e.png" type="image/png" />
  7. </head>

manifest.json

  1. {
  2. "name": "Minimal PWA", // 必填 显示的插件名称
  3. "short_name": "PWA Demo", // 可选 在APP launcher和新的tab页显示,如果没有设置,则使用name
  4. "description": "The app that helps you understand PWA", //用于描述应用
  5. "display": "standalone", // 定义开发人员对Web应用程序的首选显示模式。standalone模式会有单独的
  6. "start_url": "/", // 应用启动时的url
  7. "theme_color": "#313131", // 桌面图标的背景色
  8. "background_color": "#313131", // 为web应用程序预定义的背景颜色。在启动web应用程序和加载应用程序的内容之间创建了一个平滑的过渡。
  9. "icons": [ // 桌面图标,是一个数组
  10. {
  11. "src": "icon/lowres.webp",
  12. "sizes": "48x48", // 以空格分隔的图片尺寸
  13. "type": "image/webp" // 帮助userAgent快速排除不支持的类型
  14. },
  15. {
  16. "src": "icon/lowres",
  17. "sizes": "48x48"
  18. },
  19. {
  20. "src": "icon/hd_hi.ico",
  21. "sizes": "72x72 96x96 128x128 256x256"
  22. },
  23. {
  24. "src": "icon/hd_hi.svg",
  25. "sizes": "72x72"
  26. }
  27. ]
  28. }

另附:

  1. Manifest参考文档:https://developer.mozilla.org/zh-CN/docs/Web/Manifest
  2.  
  3. 可以打开网站https://developers.google.cn/web/showcase/2015/chrome-dev-summit查看添加至主屏幕的动图。

5、Service Worker

SW 是什么呢?这个是离线缓存文件。我们 PWA 技术使用的就是它!SW 是浏览器在后台独立于网页运行的脚本,它打开了通向不需要网页或用户交互的功能的大门,因为使用了它,才会有的那个 Reliable 特性吧,SW 作用于 浏览器于服务器之间,相当于一个代理服务器。

  • 基本特点
  • 运行在它自己的全局脚本上下文中
  • 不绑定到具体的网页
  • 无法修改网页中的元素,因为它无法访问 DOM
  • 只能使用 HTTPS
  • 拦截进出的 HTTP 请求,从而完全控制你的网站
  • 与主JS线程独立,不会被阻塞
  • 完全异步,无法使用localStorage

功能(还是比较逆天的)

  • 后台数据的同步
  • 从其他域获取资源请求
  • 接受计算密集型数据的更新,多页面共享该数据
  • 客户端编译与依赖管理
  • 后端服务的hook机制
  • 根据URL模式,自定义模板
  • 性能优化
  • 消息推送
  • 定时默认更新
  • 地理围栏

生命周期:

  • Parsed ( 解析成功 ): 首次注册 SW 时,浏览器解决脚本并获得入口点,如果解析成功,就可以访问到 SW 注册对象,在这一点中我们需要在 HTML 页面中添加一个判断,判断该浏览器是否支持 SW 。
  • Installing ( 正在安装 ):SW 脚本解析完成之后,浏览器会尝试进行安装,installing 中 install 事件被执行,如果其中有 event.waitUntil ( ) 方法,则 installing 事件会一直等到该方法中的 Promise 完成之后才会成功,如果 Promise 被拒绝,则安装失败,SW会进入 Redundant( 废弃 )状态。
  • Installed / Waiting (安装成功/等待中):如果安装成功,SW 将会进入这个状态。
  • Activating ( 正在激活 ):处于 waiting 状态的 SW 发生以下情况,将会进入 activating 状态中:

    当前已无激活状态的 worker 、 SW脚本中的 self.skipWaiting()方法被调用 ( ps: self 是 SW 中作用于全局的对象,这个方法根据英文翻译过来也能明白什么意思啦,跳过等待状态 )、用户已关闭 SW 作用域下的所有页面,从而释放了当前处于激活状态的 worker、超出指定时间,从而释放当前处于激活状态的 worker

  • Activated ( 激活成功 ):该状态,其成功接收了 document 全面控制的激活态 worker 。
  • Redundant ( 废弃 ):这个状态的出现时有原因的,如果 installing 事件失败或者 activating 事件失败或者新的 SW 替换其成为激活态 worker 。installing 事件失败和 activating 事件失败的信息我们可以在 Chrome 浏览器的 DevTools 中查看

如果上个图不好理解,可以看这个,把它的生命周期看成红绿灯:

  • register (需要下载和解析,红灯)
  • install (执行,黄灯)
  • activated( 成功,绿灯)

  

当用户首次导航至 URL 时,服务器会返回响应的网页

  • 第1步:当你调用 register() 函数时, Service Worker 开始下载。
  • 第2步:在注册过程中,浏览器会下载、解析并执行 Service Worker ()。如果在此步骤中出现任何错误,register() 返回的 promise 都会执行 reject 操作,并且 Service Worker 会被废弃。
  • 第3步:一旦 Service Worker 成功执行了,install 事件就会激活
  • 第4步:安装完成,Service Worker 便会激活,并控制在其范围内的一切。如果生命周期中的所有事件都成功了,Service Worker 便已准备就绪,随时可以使用了!

6、实现离线缓存

index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Hello Caching World!</title>
  6. </head>
  7. <body>
  8. <!-- Image -->
  9. <img src="/images/hello.png" />
  10. <!-- JavaScript -->
  11. <script async src="/js/script.js"></script>
  12. <script>
  13. // 注册 service worker
  14. if ('serviceWorker' in navigator) {
  15. navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then(function (registration) {
  16. // 注册成功
  17. console.log('ServiceWorker registration successful with scope: ', registration.scope);
  18. }).catch(function (err) {
  19. // 注册失败 :(
  20. console.log('ServiceWorker registration failed: ', err);
  21. });
  22. }
  23. </script>
  24. </body>
  25. </html>

注意:

  1. 注:Service Worker 的注册路径决定了其 scope 默认作用页面的范围。
  2. 如果 service-worker.js 是在 /sw/ 页面路径下,这使得该 Service Worker 默认只会收到 页面/sw/ 路径下的 fetch 事件。
  3. 如果存放在网站的根路径下,则将会收到该网站的所有 fetch 事件。
  4. 如果希望改变它的作用域,可在第二个参数设置 scope 范围。示例中将其改为了根目录,即对整个站点生效。

service-worker.js

  1. var cacheName = 'helloWorld'; // 缓存的名称
  2. // install 事件,它发生在浏览器安装并注册 Service Worker 时
  3. self.addEventListener('install', event => {
  4. /* event.waitUtil 用于在安装成功之前执行一些预装逻辑
  5. 但是建议只做一些轻量级和非常重要资源的缓存,减少安装失败的概率
  6. 安装成功后 ServiceWorker 状态会从 installing 变为 installed */
  7. event.waitUntil(
  8. caches.open(cacheName)
  9. .then(cache => cache.addAll([ // 如果所有的文件都成功缓存了,便会安装完成。如果任何文件下载失败了,那么安装过程也会随之失败。
  10. '/js/script.js',
  11. '/images/hello.png'
  12. ]))
  13. );
  14. });
  15.  
  16. /**
  17. 为 fetch 事件添加一个事件监听器。接下来,使用 caches.match() 函数来检查传入的请求 URL 是否匹配当前缓存中存在的任何内容。如果存在的话,返回缓存的资源。
  18. 如果资源并不存在于缓存当中,通过网络来获取资源,并将获取到的资源添加到缓存中。
  19. */
  20. self.addEventListener('fetch', function (event) {
  21. event.respondWith(
  22. caches.match(event.request)
  23. .then(function (response) {
  24. if (response) {
  25. return response;
  26. }
  27. var requestToCache = event.request.clone(); //
  28. return fetch(requestToCache).then(
  29. function (response) {
  30. if (!response || response.status !== 200) {
  31. return response;
  32. }
  33. var responseToCache = response.clone();
  34. caches.open(cacheName)
  35. .then(function (cache) {
  36. cache.put(requestToCache, responseToCache);
  37. });
  38. return response;
  39. })
  40. );
  41. });

注意:

  1. 注:为什么用request.clone()和response.clone()
  2. 需要这么做是因为requestresponse是一个流,它只能消耗一次。因为我们已经通过缓存消耗了一次,然后发起 HTTP 请求还要再消耗一次,所以我们需要在此时克隆请求
  3. Clone the requesta request is a stream and can only be consumed once.

7、消息推送

  • 步骤一、提示用户并获得他们的订阅详细信息
  • 步骤二、将这些详细信息保存在服务器上
  • 步骤三、在需要时发送任何消息

前两步:

index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Progressive Times</title>
  6. <link rel="manifest" href="/manifest.json">
  7. </head>
  8. <body>
  9. <script>
  10. var endpoint;
  11. var key;
  12. var authSecret;
  13. var vapidPublicKey = 'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY';
  14. // 方法很复杂,但是可以不用具体看,知识用来转化vapidPublicKey用
  15. function urlBase64ToUint8Array(base64String) {
  16. const padding = '='.repeat((4 - base64String.length % 4) % 4);
  17. const base64 = (base64String + padding)
  18. .replace(/\-/g, '+')
  19. .replace(/_/g, '/');
  20. const rawData = window.atob(base64);
  21. const outputArray = new Uint8Array(rawData.length);
  22. for (let i = 0; i < rawData.length; ++i) {
  23. outputArray[i] = rawData.charCodeAt(i);
  24. }
  25. return outputArray;
  26. }
  27. if ('serviceWorker' in navigator) {
  28. navigator.serviceWorker.register('sw.js').then(function (registration) {
  29. return registration.pushManager.getSubscription()
  30. .then(function (subscription) {
  31. if (subscription) {
  32. return;
  33. }
  34. return registration.pushManager.subscribe({
  35. userVisibleOnly: true,
  36. applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
  37. })
  38. .then(function (subscription) {
  39. var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
  40. key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
  41. var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
  42. authSecret = rawAuthSecret ?
  43. btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
  44. endpoint = subscription.endpoint;
  45. return fetch('./register', {
  46. method: 'post',
  47. headers: new Headers({
  48. 'content-type': 'application/json'
  49. }),
  50. body: JSON.stringify({
  51. endpoint: subscription.endpoint,
  52. key: key,
  53. authSecret: authSecret,
  54. }),
  55. });
  56. });
  57. });
  58. }).catch(function (err) {
  59. // 注册失败 :(
  60. console.log('ServiceWorker registration failed: ', err);
  61. });
  62. }
  63. </script>
  64. </body>
  65. </html>

步骤三:

app.js

  1. const webpush = require('web-push');
  2. const express = require('express');
  3. var bodyParser = require('body-parser');
  4. const app = express();
  5. webpush.setVapidDetails(
  6. 'mailto:contact@deanhume.com',
  7. 'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY',
  8. 'p6YVD7t8HkABoez1CvVJ5bl7BnEdKUu5bSyVjyxMBh0'
  9. );
  10. app.post('/register', function (req, res) {
  11. var endpoint = req.body.endpoint;
  12. saveRegistrationDetails(endpoint, key, authSecret);
  13. const pushSubscription = {
  14. endpoint: req.body.endpoint,
  15. keys: {
  16. auth: req.body.authSecret,
  17. p256dh: req.body.key
  18. }
  19. };
  20. var body = 'Thank you for registering';
  21. var iconUrl = 'https://example.com/images/homescreen.png';
  22. // 发送 Web 推送消息
  23. webpush.sendNotification(pushSubscription,
  24. JSON.stringify({
  25. msg: body,
  26. url: 'http://localhost:3111/',
  27. icon: iconUrl
  28. }))
  29. .then(result => res.sendStatus(201))
  30. .catch(err => {
  31. console.log(err);
  32. });
  33. });
  34. app.listen(3111, function () {
  35. console.log('Web push app listening on port 3111!')
  36. });

service worker监听push事件,将通知详情推送给用户

service-worker.js

  1. self.addEventListener('push', function (event) {
  2. // 检查服务端是否发来了任何有效载荷数据
  3. var payload = event.data ? JSON.parse(event.data.text()) : 'no payload';
  4. var title = 'Progressive Times';
  5. event.waitUntil(
  6. // 使用提供的信息来显示 Web 推送通知
  7. self.registration.showNotification(title, {
  8. body: payload.msg,
  9. url: payload.url,
  10. icon: payload.icon
  11. })
  12. );
  13. });

8、PWA小demo

准备:

  创建一个关于PWA项目的文件夹

  文件夹内准备一张图

  一个index.html文件

  一个main.css文件

  一个manifest.json文件

  一个sw.js文件

以后更新把,写崩了,我再寻思寻思


分割线,可爱的我来更新小栗子了

css文件夹里有一个style.css

images里有一个logo.jpg

具体看代码:

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>Hello PWA</title>
  8. <link rel="stylesheet" href="css/style.css">
  9.  
  10. </head>
  11. <body>
  12. <img src="data:images/logo.jpg">
  13.  
  14. <script>
  15. if ('serviceWorker' in navigator) { // 浏览器支持SW
  16. navigator.serviceWorker.register('sw.js').then(function (registration) {
  17. console.log('ServiceWorker registration successful with scope: ', registration.scope);
  18. }).catch(function (err) {
  19. console.log('ServiceWorker registration failed: ', err);
  20. });
  21. }
  22. </script>
  23. </body>
  24. </html>

我的style.css是空的哈哈哈

sw.js

  1. var cacheName = 'hello-pwa';
  2.  
  3. self.addEventListener('install', event => {
  4. event.waitUntil(
  5. caches.open(cacheName)
  6. .then(cache => cache.addAll(
  7. [
  8. '/', // 这个一定要包含整个目录,不然无法离线浏览
  9. './images/logo.jpg',
  10. './index.html',
  11. './css/style.css'
  12. ]
  13. )).then(() => self.skipWaiting())
  14. );
  15. });
  16.  
  17. self.addEventListener('fetch', function (event) {
  18. event.respondWith(
  19. caches.match(event.request)
  20. .then(function (response) {
  21. if (response) {
  22. return response;
  23. }
  24. return fetch(event.request);
  25. })
  26. );
  27. });

接下来通过 http-server 和 ngrok(https)进行调试查看

在当前文件下 
安装 http-server

  1. npm install http-server -g

安装 ngrok,下载解压即可

在项目目录下执行如下命令:

  1. http-server -c-1 // -c-1 会关闭缓存

再开启另外一个终端在 ngrok 文件的目录下执行如下命令:

  1. ./ngrok http 8080 // http-server 默认开启8080端口

运行:(我端口8080被占了,所以这里是8081)

查看application

查看缓存部分

第一次加载进来缓存没有东西,需要刷新一下页面。

https://github.com/yangTwo100/PWA_search_demo

以上。

H5 PWA技术以及小demo的更多相关文章

  1. Flexible实现H5移动端适配小demo

    前言 看了宇哥关于移动端适配的分享后,加上目前公司项目也需要做移动端适配,今天就抽空搞了搞.目前业界还是比较推崇手淘使用"rem+viewport"的解决方案,今天自己模仿手淘fl ...

  2. Java导出页面数据或数据库数据至Excel文件并下载,采用JXL技术,小demo(servlet实现)

    public class ExportExcelServlet extends HttpServlet { /** * */ private static final long serialVersi ...

  3. js+canvas(H5)实现小球移动小demo

    *canvas提供画布,大小自定义,js得到画布,从画布对象通过getContext('2d')来得到画笔,然后就可以开始画了 代码: <!DOCTYPE html> <html l ...

  4. 入门Leaflet之小Demo

    入门Leaflet之小Demo 写在前面 ---- WebGIS开发基础之Leaflet GIS基本概念:GIS.Map.Layer.Feature.Geometry.Symbol.Data(Poin ...

  5. 带你使用h5开发移动端小游戏

    带你使用h5开发移动端小游戏 在JY1.x版本中,你要做一个pc端的小游戏,会非常的简单,包括说,你要在低版本的浏览器IE8中,也不会出现明显的卡顿现象,你只需要关心游戏的逻辑就行了,比较适合逻辑较为 ...

  6. React问答小demo

    在学习react初期,看了一些视频和资料,react基础知识差不多学完,跟着网上的一个教程,做了一个小型的问答demo. 需求看图说: 1.点击"添加"按钮,显示问题输入表单,再次 ...

  7. 学习react,动手实现一个小demo(仿知乎问答)

    学习react也有一周的时间,最近自己做了个仿知乎问答的小demo,项目源码在github上:https://github.com/yang302/reactQa 使用技术:bower+gulp+re ...

  8. 一周一个小demo — 前端后台的交互实例

    这一周呢,本K在大神的指导下,完成了一个利用ajax与php文件上传处理相结合的一个留言板功能的小实例,下面就让本K来带大家瞅瞅如何实现这一种功能. 一.界面概览 首先我们来看一下这个小demo的具体 ...

  9. 腾讯云 Game-Tech 技术沙龙小游戏专场“空降”长沙

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由腾讯游戏云发表于云+社区专栏 小游戏作为今年快速成长的新生态,在开放进入市场之后持续成为行业热点,获得了游戏开发商的高度关注与参与.在 ...

随机推荐

  1. 能改变this各种情况下的总结,还有没有总结到的,请留言!!

    1.. 在函数参数中的,回调函数的this ,指向window 如: promise中的回调函数, 可以在方法外,转存this 2..构造函数中,this指向,实例对象  , 在全局中this是win ...

  2. 贪心 —— 今年暑假不AC

    贪心基本题, 有助于理解贪心算法的思想 #include <cstdio> #include <algorithm> using namespace std; struct P ...

  3. 《剑指offer》第五十三题(0到n-1中缺失的数字)

    // 面试题53(二):0到n-1中缺失的数字 // 题目:一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字 // 都在范围0到n-1之内.在范围0到n-1的n个数字中有且只有一个数 ...

  4. ImageConverter引起的 invalid address or address of corrupt block 0xb7feab58 passed to dlfree

    虹软人脸识别,其方法要传NV21格式的byte[], github上有一个虹软的Demo,是不是虹软工作人员写的不清楚,这个Demo里bitmap转NV21格式byte[]用的是一个第三方库https ...

  5. MYSQL常用函数(时间和日期函数)Java中

    CURDATE()或CURRENT_DATE() 返回当前的日期 CURTIME()或CURRENT_TIME() 返回当前的时间 DATE_ADD(date,INTERVAL int keyword ...

  6. 移动端rem适配布局

    dome如下: <!doctype html><html><head> <meta charset="UTF-8" /> <m ...

  7. 雷林鹏分享:XML 元素

    XML 元素 XML 文档包含 XML 元素. 什么是 XML 元素? XML 元素指的是从(且包括)开始标签直到(且包括)结束标签的部分. 一个元素可以包含: 其他元素 文本 属性 或混合以上所有. ...

  8. 女性睾酮水平高(High Testosterone Levels in Women)

    在外国网站了解睾酮高的一些资料,顺便记录生活点滴. 摘自网址:https://www.healthline.com/health/high-testosterone-in-women 高睾酮的妇女 睾 ...

  9. 『计算机视觉』Mask-RCNN_推断网络其二:基于ReNet101的FPN共享网络暨TensorFlow和Keras交互简介

    零.参考资料 有关FPN的介绍见『计算机视觉』FPN特征金字塔网络. 网络构架部分代码见Mask_RCNN/mrcnn/model.py中class MaskRCNN的build方法的"in ...

  10. python – time.sleep – 睡眠线程

    import time from threading import Thread class worker(Thread): def run(self): for x in xrange(0,11): ...