H5 PWA 渐进式 Web 应用

举报
yd_217846120 发表于 2026/09/28 08:47:50 2026/09/28
【摘要】 H5 PWA 渐进式 Web 应用摘要:PWA 让网页具备可安装、离线可用、推送通知等原生能力。本文从 manifest、Service Worker、缓存策略、推送、安装到调试,系统讲解如何把普通网站升级为 PWA。 一、PWA 是什么PWA(Progressive Web App)是一组技术组合,让 Web 应用在体验上接近原生:可安装到主屏,启动无浏览器外壳。离线可用,弱网下仍能访问...

H5 PWA 渐进式 Web 应用

摘要:PWA 让网页具备可安装、离线可用、推送通知等原生能力。本文从 manifest、Service Worker、缓存策略、推送、安装到调试,系统讲解如何把普通网站升级为 PWA。

一、PWA 是什么

PWA(Progressive Web App)是一组技术组合,让 Web 应用在体验上接近原生:

  • 可安装到主屏,启动无浏览器外壳。
  • 离线可用,弱网下仍能访问。
  • 支持推送通知与后台同步。
  • 跨平台,一套代码运行所有设备。

核心三件套:Web App Manifest、Service Worker、HTTPS(本地开发 localhost 例外)。

二、Web App Manifest

在 HTML 中引用:

<link rel="manifest" href="/manifest.json">

manifest.json:

{
  "name": "京行 · 北京旅游",
  "short_name": "京行",
  "description": "北京旅游推荐指南",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "orientation": "portrait",
  "background_color": "#1A1A1A",
  "theme_color": "#D4A84B",
  "lang": "zh-CN",
  "icons": [
    { "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
  ],
  "shortcuts": [
    { "name": "必游景点", "url": "/#destinations", "icons": [{ "src": "/icons/spot.png", "sizes": "96x96" }] }
  ]
}

关键字段:

  • display:standalone 隐藏浏览器 UI;fullscreen 全屏;minimal-ui 保留最少控件。
  • theme_color:影响状态栏与窗口边框颜色。
  • purpose: maskable:让图标可被系统裁剪到安全区域,避免被切圆角时缺角。
  • shortcuts:长按图标快捷菜单。

三、Service Worker 基础

SW 是运行在主线程之外的脚本,可拦截网络请求、管理缓存、接收推送。

注册:

if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker.register("/sw.js").then(reg => {
      console.log("注册成功", reg.scope);
    });
  });
}

生命周期:install → installed → activating → activated → redundant。

四、缓存策略

预缓存

const CACHE = "v1";
const ASSETS = ["/", "/index.html", "/styles.css", "/script.js"];

self.addEventListener("install", e => {
  e.waitUntil(caches.open(CACHE).then(c => c.addAll(ASSETS)));
});

self.addEventListener("activate", e => {
  e.waitUntil(
    caches.keys().then(keys => Promise.all(
      keys.filter(k => k !== CACHE).map(k => caches.delete(k))
    ))
  );
});

install 时预缓存核心资源,activate 时清理旧缓存。

拦截请求

self.addEventListener("fetch", e => {
  e.respondWith(
    caches.match(e.request).then(cached => {
      return cached || fetch(e.request).then(resp => {
        const copy = resp.clone();
        caches.open(CACHE).then(c => c.put(e.request, copy));
        return resp;
      });
    })
  );
});

这是"缓存优先,回退网络"策略。不同资源适合不同策略:

  • HTML:网络优先(保证最新),失败回退缓存。
  • CSS/JS/图片:缓存优先(带 hash 时长缓存)。
  • API:stale-while-revalidate(先返回缓存,同时更新)。

Stale While Revalidate

self.addEventListener("fetch", e => {
  e.respondWith(
    caches.open(CACHE).then(async c => {
      const cached = await c.match(e.request);
      const fetchPromise = fetch(e.request).then(resp => {
        c.put(e.request, resp.clone());
        return resp;
      });
      return cached || fetchPromise;
    })
  );
});

兼顾速度与新鲜度,适合 API 数据。

五、用 Workbox 简化

手写 SW 容易出错,Google 的 Workbox 库封装了常用策略:

import { registerRoute } from "workbox-routing";
import { CacheFirst, StaleWhileRevalidate, NetworkFirst } from "workbox-strategies";
import { precacheAndRoute } from "workbox-precaching";

precacheAndRoute(self.__WB_MANIFEST);

registerRoute(
  ({ request }) => request.destination === "image",
  new CacheFirst({ cacheName: "images" })
);

registerRoute(
  ({ url }) => url.pathname.startsWith("/api/"),
  new StaleWhileRevalidate({ cacheName: "api" })
);

registerRoute(
  ({ request }) => request.mode === "navigate",
  new NetworkFirst({ cacheName: "pages" })
);

配合构建插件自动生成预缓存清单,省去手写资源列表。

六、离线兜底

const OFFLINE_URL = "/offline.html";

self.addEventListener("fetch", e => {
  if (e.request.mode === "navigate") {
    e.respondWith(
      fetch(e.request).catch(() => caches.match(OFFLINE_URL))
    );
  }
});

离线时返回一个友好的兜底页,而非浏览器默认错误。

七、推送通知

订阅

const reg = await navigator.serviceWorker.ready;
const sub = await reg.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC)
});
await fetch("/api/subscribe", { method: "POST", body: JSON.stringify(sub) });

接收

self.addEventListener("push", e => {
  const data = e.data.json();
  self.registration.showNotification(data.title, {
    body: data.body,
    icon: "/icons/192.png",
    badge: "/icons/badge.png",
    data: { url: data.url }
  });
});

self.addEventListener("notificationclick", e => {
  e.notification.close();
  e.waitUntil(clients.openWindow(e.notification.data.url));
});

服务端用 Web Push 协议 + VAPID 密钥推送,浏览器收到后唤醒 SW 触发通知。

八、后台同步

// 注册同步
const reg = await navigator.serviceWorker.ready;
reg.sync.register("send-outbox");

// SW 中处理
self.addEventListener("sync", e => {
  if (e.tag === "send-outbox") {
    e.waitUntil(sendOutbox());
  }
});

用户离线时把请求存到 IndexedDB,恢复网络后 SW 自动重试,无需用户打开页面。

九、安装提示

监听 beforeinstallprompt 自定义安装按钮:

let deferredPrompt;
window.addEventListener("beforeinstallprompt", e => {
  e.preventDefault();
  deferredPrompt = e;
  showInstallButton();
});

installButton.addEventListener("click", async () => {
  deferredPrompt.prompt();
  const { outcome } = await deferredPrompt.userChoice;
  if (outcome === "accepted") {
    trackInstall();
  }
  deferredPrompt = null;
});

iOS Safari 不支持该事件,需用引导提示让用户手动"添加到主屏"。

十、检测与调试

  • Chrome DevTools → Application → Manifest 查看清单与安装。
  • Application → Service Workers 查看注册状态、更新、绕过网络。
  • Lighthouse PWA 评分给出完整检查项。
  • 用 workbox-window 监听 SW 更新提示用户刷新。

十一、更新策略

SW 默认在下次启动时激活旧版本仍服务本次会话。要即时更新:

self.addEventListener("install", e => self.skipWaiting());
self.addEventListener("activate", e => e.waitUntil(clients.claim()));

配合 navigator.serviceWorker.addEventListener("controllerchange", () => location.reload()) 提示用户刷新。生产中更稳妥的是显示"有新版本"提示,让用户主动刷新,避免中途刷新打断操作。

十二、总结

PWA 用 Web 技术提供接近原生的体验,关键在 Manifest + Service Worker + 缓存策略三件套。建议:用 Workbox 管缓存、按资源类型选策略、提供离线兜底页、用真实用户数据验证效果。PWA 不是要替代原生,而是让 Web 在合适场景下足够好用——内容型应用、工具型应用尤其适合。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。