PWA集成和离线使用

最近项目需要,需要将h5代码集成到PWA,以此记录下。

mdn上早已有了介绍,用的时候才发现这门技术

这是google介绍的详细地址传送门

引言

我们知道,在chrome(等一些现代浏览器)中,你可以将访问的网站添加到桌面,这样就会在桌面生成一个类似"快捷方式"的图标,当你点击该图标时,便可以快速访问该网站(Web App)。

对于PWA来说,有一些重要的特性:

  • Web App可以被添加到桌面并有它自己的应用图标
  • 同时,从桌面开启时,会和原生app一样有它自己的"开屏图"
  • 更进一步的,这个Web App在的样子几乎和原生应用一样------没有浏览器的地址栏、工具条,似乎和Native App一样运行在一个独立的容器中。

1. 新建mainfest.json文件

Manifest是一个JSON格式的文件,你可以把它理解为一个指定了Web App桌面图标、名称、开屏图标、运行模式等一系列资源的一个清单。

manifest 的目的是将Web应用程序安装到设备的主屏幕,为用户提供更快的访问和更丰富的体验。 ------ MDN

json 复制代码
  {
    "name": "pwa测试",
    "short_name": "pwa",
    "start_url": "/",
    "display": "standalone",
    "background_color": "#333",
    "description": "这是描述",
    "orientation": "portrait-primary",
    "theme_color": "#5eace0",
    "icons": [{
        "src": "img/icons/book-32.png",
        "sizes": "32x32",
        "type": "image/png"
    }, {
        "src": "img/icons/book-72.png",
        "sizes": "72x72",
        "type": "image/png"
    }, {
        "src": "img/icons/book-128.png",
        "sizes": "128x128",
        "type": "image/png"
    }, {
        "src": "img/icons/book-144.png",
        "sizes": "144x144",
        "type": "image/png"
    }, {
        "src": "img/icons/book-192.png",
        "sizes": "192x192",
        "type": "image/png"
    }, {
        "src": "img/icons/book-256.png",
        "sizes": "256x256",
        "type": "image/png"
    }, {
        "src": "img/icons/book-512.png",
        "sizes": "512x512",
        "type": "image/png"
    }]
}
  • name, short_name 指定了Web App的名称。short_name其实是该应用的一个简称。一般来说,当没有足够空间展示应用的name时,系统就会使用short_name
  • start_url 指定了用户打开该Web App时加载的URL。相对URL会相对于manifest。这里我们指定了start_url为/,访问根目录。
  • display display控制了应用的显示模式,它有四个值可以选择:fullscreen、standalone、minimal-ui和browser。
    • [ ] fullscreen:全屏显示,会尽可能将所有的显示区域都占满;
    • [ ] standalone:独立应用模式,这种模式下打开的应用有自己的启动图标,并且不会有浏览器的地址栏。因此看起来更像一个Native App;
    • [ ] minimal-ui:与standalone相比,该模式会多出地址栏;
    • [ ] browser:一般来说,会和正常使用浏览器打开样式一致。
  • orientation 控制Web App的方向。设置某些值会具有类似锁屏的效果(禁止旋转),例如例子中的portrait-primary。具体的值包括:any, natural, landscape, landscape-primary, landscape-secondary, portrait, portrait-primary, portrait-secondary
  • icons 用来指定应用的桌面图标。icons本身是一个数组,每个元素包含三个属性
    • [ ] sizes:图标的大小。通过指定大小,系统会选取最合适的图标展示在相应位置上。
    • [ ] src:图标的文件路径。注意相对路径是相对于manifest。
    • [ ] type:图标的图片类型。
  • background_color 是在应用的样式资源为加载完毕前的默认背景,因此会展示在开屏界面。也就是我们常说的启动页
  • theme_color 定义应用程序的默认主题颜色。 这有时会影响操作系统显示应用程序的方式(例如,在Android的任务切换器上,主题颜色包围应用程序)。
  • description 应用的描述

配置完manifest文件后,在head中添加一个link标签:

html 复制代码
  <!-- 在index.html中添加以下meta标签 -->
<link rel="manifest" href="/manifest.json">

2. service-worker.js 离线使用

首先需要缓存静态资源(sw.js)

javascript 复制代码
  const cacheName = 'pwa-cache-v1';
  const filesToCache = [
      '/index.html'
  ];
  // 监听install事件,安装完成后,进行文件缓存
  self.addEventListener('install', function (event) {
      console.log('Service Worker 安装成功');
      event.waitUntil(
          caches.open(cacheName)
              .then(function (cache) {
                  return cache.addAll(filesToCache);
              })
      );
  });

  self.addEventListener('fetch', function (event) {
      // console.log('Service Worker 拦截到请求:', event.request);
      event.respondWith(
          caches.match(event.request)
              .then(function (response) {
                  // 如果缓存中有请求的资源,则直接返回缓存中的资源
                  // if (response) {
                  //     return response;
                  // }
                  // 否则通过网络获取资源
                  return fetch(event.request);
              })
      );
  });

  self.addEventListener('activate', function (event) {
      console.log('Service Worker 激活成功');
      event.waitUntil(
          caches.keys().then(function (cacheNames) {
              return Promise.all(
                  cacheNames.filter(function (cacheName) {
                      // 清理旧版本缓存
                      return cacheName.startsWith('pwa-cache-') && cacheName !== 'pwa-cache-v1';
                  }).map(function (cacheName) {
                      return caches.delete(cacheName);
                  })
              );
          })
      );
  });

然后注册在index.js中来注册我们的Service Worke

javascript 复制代码
// index.js
// 注册service worker,service worker脚本文件为sw.js
if ("serviceWorker" in navigator && 'PushManager' in window) {
  window.addEventListener('load', async () => {
    navigator.serviceWorker.register("/sw.js").then(function(registration) {
        console.log(
          "ServiceWorker registration successful with scope: ",
          registration.scope
        );
      })
      .catch(function(err) {
        console.error(
          "ServiceWorker registration failed: ",
          err
        );
      });
  })
}

这样后webapp可以离线使用

3. beforeinstallprompt触发安装

通过beforeinstallprompt事件,我们可以在用户点击安装按钮之前,弹出一个对话框,提示用户是否安装。

javascript 复制代码
  window.addEventListener(
    "beforeinstallprompt",
    function(event) {
      console.log("支持安装 PWA:", event);
      // 阻止默认的安装提示
      event.preventDefault();
      // 保存事件以在后面触发安装时使用
      deferredPrompt = event;
    }
  );

如果未安装则调用deferredPrompt.prompt()弹出安装对话框,然后根据deferredPrompt.userChoice 判断用户安装后的结果,如果为accepted则用户同意安装,否则拒绝。

以下是完整代码

javascript 复制代码
// 监听 beforeinstallprompt 事件
let deferredPrompt

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {

    var installButton = document.getElementById("installButton");
    // 添加按钮点击事件
    installButton.addEventListener("click", function() {
      deferredPromptAction();
    });

    // 监听 beforeinstallprompt 事件
    window.addEventListener(
      "beforeinstallprompt",
      function(event) {
        console.log("支持安装 PWA:", event);
        // 阻止默认的安装提示
        event.preventDefault();
        // 保存事件以在后面触发安装时使用
        deferredPrompt = event;
      }
    );

    window.addEventListener("appinstalled", function(event) {
      // 应用程序安装完成后的操作
      console.log("应用程序已安装完成!");
    });
    if (localStorage.getItem("allInstall")) {
      console.log("已经安装 PWA,在web中");
    } else {
      console.log("尚未安装 PWA,在web中");
    }

    function deferredPromptAction() {
      console.log("click:", deferredPrompt);
      if (deferredPrompt) {
        console.log("浏览器支持 PWA");
        // 触发安装提示
        deferredPrompt.prompt();
        // 等待用户作出安装决定
        deferredPrompt.userChoice.then(function(choiceResult) {
          if (choiceResult.outcome === "accepted") {
            console.log("用户已接受安装提示");
            localStorage.setItem("allInstall", true);
            if (
              window.matchMedia(
                "(display-mode: standalone)"
              ).matches ||
              window.navigator.standalone === true
            ) {
              console.log("已经在PWA");
              location.href = openUrl;
            } else {
              console.log("尚未安装 PWA");
              startCount();
            }
          } else {
            console.log("用户拒绝了安装提示");
          }
          // 清除安装提示
          deferredPrompt = null;
        });
      } else {
        console.log("浏览器不支持 PWA");
        // startCount();
        openInChrome();
        // window.location.href = openUrl;
      }
    }

    function openInChrome() {
      // `googlechrome://${e.replace(/(https|http):\/\//, "")}`
      // 当前页面的 URL
      var currentUrl = window.location.origin;
      // 构建要传递给谷歌浏览器的 intent URI
      // var intentUrl = `intent://${currentUrl.replace(
      //     /(https|http):\/\//,
      //     ""
      // )}#Intent;scheme=https;action=android.intent.action.VIEW;component=com.android.chrome;end`;
      var intentUrl =
        "intent://open/#Intent;scheme=" +
        encodeURIComponent(currentUrl) +
        ";package=com.android.chrome;end";
      console.log("openInChrome:", intentUrl);
      // 尝试在谷歌浏览器中打开当前页面
      window.location.href = intentUrl;
    }
  });
}

关于beforeinstallprompt在MDN有说明,不清楚的小伙伴可以点击查看传送门

关于在ios和window上的兼容性,还是有差异的,但网上还是有办法的,这边暂时不做描述,小伙伴可以自行查看下。

好了暂时到这里了,因为我这边的项目是uniapp开发的,集成还需要自定义模板一些功能,如果有uniapp集成到PWA的伙伴不知道怎么实现,可以给我留言,由于时间问题,没有一一例出来。

相关推荐
程序员-张师傅25 分钟前
深入浅出:npm常用命令详解与实践
前端·npm·node.js
因为奋斗超太帅啦37 分钟前
ES6模块化学习
前端·学习·es6
睿智的海鸥1 小时前
html+js+css在线倒计时
前端·javascript·css·html
WujieLi1 小时前
2024 年中复盘:想法太多,落地太少
前端
ct10270385271 小时前
前端工程师
前端
曼诺尔雷迪亚兹1 小时前
VueQuill 富文本编辑器技术文档快速上手
前端·javascript·vue.js
喵个咪1 小时前
Flutter 使用 RxDart & Streams 实现 BLoC模式
前端·flutter·reactivex
Sarah_1 小时前
android Dialog沉浸式状态栏的实现
android·java·前端
@PHARAOH2 小时前
WHAT - React useReducer vs Redux
前端·react.js·前端框架
冷水无言2 小时前
前端打包性能优化-Vue3+vite 实践指南
前端·vue.js·vite