Skip to main content

渐进式Web应用(PWA)详解

渐进式Web应用(Progressive Web App)是一种使用现代Web技术构建的应用程序,提供类似原生应用的用户体验。PWA结合了Web和原生应用的最佳特性,可以在任何设备上运行,并提供离线功能、推送通知和安装体验。

核心价值

PWA = Web技术 + 原生体验 + 渐进增强 + 跨平台

  • 🌐 Web技术基础:使用HTML、CSS、JavaScript构建
  • 📱 原生应用体验:全屏显示、启动画面、应用图标
  • 🔄 离线功能:Service Workers实现离线缓存和同步
  • 🔔 推送通知:实时消息推送,提升用户参与度
  • 📦 可安装性:可添加到主屏幕,无需应用商店
  • 渐进增强:在支持的浏览器上提供更好的体验

1. PWA核心概念

1.1 PWA特性概览

PWA具有以下核心特性,使其能够提供接近原生应用的体验:

1.2 PWA技术栈

技术组件作用必需性浏览器支持
Web应用清单定义应用元数据和安装行为必需广泛支持
Service Workers离线缓存、后台同步、推送通知必需现代浏览器
HTTPS安全上下文要求必需所有浏览器
响应式设计适配不同设备推荐所有浏览器
App Shell架构快速加载的应用外壳推荐所有浏览器

2. Service Workers详解

2.1 Service Workers基础

Service Workers是PWA的核心技术,运行在后台的JavaScript脚本,充当Web应用和网络之间的代理。

Service Worker注册与生命周期

Service Worker注册
javascript
1// main.js - 主应用文件
2if ('serviceWorker' in navigator) {
3 window.addEventListener('load', async () => {
4 try {
5 const registration = await navigator.serviceWorker.register('/sw.js', {
6 scope: '/' // Service Worker的作用域
7 });
8
9 console.log('Service Worker注册成功:', registration.scope);
10
11 // 监听Service Worker状态变化
12 registration.addEventListener('updatefound', () => {
13 const newWorker = registration.installing;
14 console.log('发现新的Service Worker');
15
16 newWorker.addEventListener('statechange', () => {
17 console.log('Service Worker状态:', newWorker.state);
18
19 if (newWorker.state === 'installed') {
20 if (navigator.serviceWorker.controller) {
21 // 有新版本可用
22 showUpdateAvailableNotification();
23 } else {
24 // 首次安装完成
25 showCachedNotification();
26 }
27 }
28 });
29 });
30
31 // 监听Service Worker控制器变化
32 navigator.serviceWorker.addEventListener('controllerchange', () => {
33 console.log('Service Worker控制器已更新');
34 window.location.reload();
35 });
36
37 } catch (error) {
38 console.error('Service Worker注册失败:', error);
39 }
40 });
41}
42
43// 显示更新可用通知
44function showUpdateAvailableNotification() {
45 const notification = document.createElement('div');
46 notification.className = 'update-notification';
47 notification.innerHTML = `
48 <div class="notification-content">
49 <p>新版本可用!</p>
50 <button onclick="updateServiceWorker()">更新</button>
51 <button onclick="dismissNotification()">稍后</button>
52 </div>
53 `;
54 document.body.appendChild(notification);
55}
56
57// 更新Service Worker
58async function updateServiceWorker() {
59 const registration = await navigator.serviceWorker.getRegistration();
60 if (registration && registration.waiting) {
61 // 向等待中的Service Worker发送消息
62 registration.waiting.postMessage({ type: 'SKIP_WAITING' });
63 }
64}
65
66// Service Worker生命周期管理
67class ServiceWorkerManager {
68 constructor() {
69 this.registration = null;
70 this.isUpdateAvailable = false;
71 }
72
73 async init() {
74 if (!('serviceWorker' in navigator)) {
75 console.warn('Service Worker不被支持');
76 return;
77 }
78
79 try {
80 this.registration = await navigator.serviceWorker.register('/sw.js');
81 this.setupEventListeners();
82 console.log('Service Worker管理器初始化成功');
83 } catch (error) {
84 console.error('Service Worker注册失败:', error);
85 }
86 }
87
88 setupEventListeners() {
89 // 监听更新
90 this.registration.addEventListener('updatefound', () => {
91 const newWorker = this.registration.installing;
92 this.trackInstalling(newWorker);
93 });
94
95 // 监听消息
96 navigator.serviceWorker.addEventListener('message', (event) => {
97 this.handleMessage(event.data);
98 });
99
100 // 监听控制器变化
101 navigator.serviceWorker.addEventListener('controllerchange', () => {
102 window.location.reload();
103 });
104 }
105
106 trackInstalling(worker) {
107 worker.addEventListener('statechange', () => {
108 if (worker.state === 'installed') {
109 this.isUpdateAvailable = true;
110 this.notifyUpdateAvailable();
111 }
112 });
113 }
114
115 notifyUpdateAvailable() {
116 // 触发自定义事件
117 window.dispatchEvent(new CustomEvent('sw-update-available'));
118 }
119
120 async skipWaiting() {
121 if (this.registration && this.registration.waiting) {
122 this.registration.waiting.postMessage({ type: 'SKIP_WAITING' });
123 }
124 }
125
126 handleMessage(data) {
127 switch (data.type) {
128 case 'SW_UPDATED':
129 console.log('Service Worker已更新');
130 break;
131 case 'CACHE_UPDATED':
132 console.log('缓存已更新');
133 break;
134 }
135 }
136}
137
138// 使用Service Worker管理器
139const swManager = new ServiceWorkerManager();
140swManager.init();
141
142// 监听更新事件
143window.addEventListener('sw-update-available', () => {
144 showUpdateAvailableNotification();
145});

3. Web应用清单(Manifest)

3.1 清单文件配置

Web应用清单是一个JSON文件,定义了PWA的元数据和行为。

基础清单配置

manifest.json
json
1{
2 "name": "我的PWA应用",
3 "short_name": "PWA应用",
4 "description": "一个功能完整的渐进式Web应用示例",
5 "start_url": "/",
6 "scope": "/",
7 "display": "standalone",
8 "orientation": "portrait-primary",
9 "theme_color": "#2196F3",
10 "background_color": "#ffffff",
11 "lang": "zh-CN",
12 "dir": "ltr",
13
14 "icons": [
15 {
16 "src": "/images/icon-72.png",
17 "sizes": "72x72",
18 "type": "image/png",
19 "purpose": "any"
20 },
21 {
22 "src": "/images/icon-96.png",
23 "sizes": "96x96",
24 "type": "image/png",
25 "purpose": "any"
26 },
27 {
28 "src": "/images/icon-128.png",
29 "sizes": "128x128",
30 "type": "image/png",
31 "purpose": "any"
32 },
33 {
34 "src": "/images/icon-144.png",
35 "sizes": "144x144",
36 "type": "image/png",
37 "purpose": "any"
38 },
39 {
40 "src": "/images/icon-152.png",
41 "sizes": "152x152",
42 "type": "image/png",
43 "purpose": "any"
44 },
45 {
46 "src": "/images/icon-192.png",
47 "sizes": "192x192",
48 "type": "image/png",
49 "purpose": "any maskable"
50 },
51 {
52 "src": "/images/icon-384.png",
53 "sizes": "384x384",
54 "type": "image/png",
55 "purpose": "any"
56 },
57 {
58 "src": "/images/icon-512.png",
59 "sizes": "512x512",
60 "type": "image/png",
61 "purpose": "any maskable"
62 }
63 ],
64
65 "categories": ["productivity", "utilities"],
66 "screenshots": [
67 {
68 "src": "/images/screenshot-mobile.png",
69 "sizes": "640x1136",
70 "type": "image/png",
71 "form_factor": "narrow"
72 },
73 {
74 "src": "/images/screenshot-desktop.png",
75 "sizes": "1280x720",
76 "type": "image/png",
77 "form_factor": "wide"
78 }
79 ]
80}

参与讨论