PWA核心特性
- 可安装:可以添加到主屏幕,像原生应用一样使用
- 离线可用:通过Service Worker提供离线功能
- 响应式设计:适配各种屏幕尺寸
- 应用外壳:快速加载的界面框架
- 推送通知:向用户发送通知消息
- 安全连接:必须使用HTTPS
Service Worker实现
注册Service Worker
// 主线程注册Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/'
});
console.log('Service Worker注册成功:', registration);
// 检查更新
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
console.log('发现新Service Worker版本');
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// 新版本已安装,提示用户刷新
showUpdateNotification();
}
});
});
} catch (error) {
console.error('Service Worker注册失败:', error);
}
});
}
Service Worker文件
// sw.js - Service Worker主文件
const CACHE_NAME = 'pwa-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
'/images/icon-192.png',
'/images/icon-512.png'
];
// 安装事件
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('缓存文件:', urlsToCache);
return cache.addAll(urlsToCache);
})
.then(() => self.skipWaiting())
);
});
// 激活事件
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
console.log('删除旧缓存:', cacheName);
return caches.delete(cacheName);
}
})
);
}).then(() => self.clients.claim())
);
});
// 获取请求
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 缓存命中
if (response) {
return response;
}
// 克隆请求
const fetchRequest = event.request.clone();
return fetch(fetchRequest).then(response => {
// 检查响应是否有效
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆响应
const responseToCache = response.clone();
// 缓存新资源
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
}).catch(() => {
// 网络失败,尝试返回离线页面
if (event.request.mode === 'navigate') {
return caches.match('/offline.html');
}
});
})
);
});
Web App Manifest
// manifest.json
{
"name": "我的PWA应用",
"short_name": "MyPWA",
"description": "一个渐进式Web应用示例",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#4a90e2",
"orientation": "portrait",
"scope": "/",
"lang": "zh-CN",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"screenshots": [
{
"src": "/screenshots/desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide"
},
{
"src": "/screenshots/mobile.png",
"sizes": "750x1334",
"type": "image/png",
"form_factor": "narrow"
}
],
"categories": ["productivity", "utilities"],
"shortcuts": [
{
"name": "新建项目",
"short_name": "新建",
"description": "创建一个新项目",
"url": "/new-project",
"icons": [{ "src": "/icons/new.png", "sizes": "96x96" }]
},
{
"name": "查看报告",
"short_name": "报告",
"description": "查看统计报告",
"url": "/reports",
"icons": [{ "src": "/icons/report.png", "sizes": "96x96" }]
}
]
}
推送通知
// 请求通知权限
async function requestNotificationPermission() {
if (!('Notification' in window)) {
console.log('当前浏览器不支持通知');
return false;
}
if (Notification.permission === 'granted') {
return true;
}
const permission = await Notification.requestPermission();
return permission === 'granted';
}
// 订阅推送
async function subscribeToPush() {
if (!('serviceWorker' in navigator)) {
console.log('Service Worker不支持');
return null;
}
if (!('PushManager' in window)) {
console.log('推送通知不支持');
return null;
}
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('你的公钥')
});
// 将subscription发送到服务器
await sendSubscriptionToServer(subscription);
return subscription;
}
// 显示通知
function showNotification(title, options = {}) {
if (!('Notification' in window) || Notification.permission !== 'granted') {
return;
}
const notification = new Notification(title, {
icon: '/icons/icon-192.png',
badge: '/icons/icon-96.png',
vibrate: [200, 100, 200],
...options
});
notification.onclick = () => {
window.focus();
notification.close();
};
}
// Service Worker中接收推送
self.addEventListener('push', event => {
const data = event.data?.json() || { title: '新通知' };
const options = {
body: data.body || '您有一条新消息',
icon: data.icon || '/icons/icon-192.png',
badge: '/icons/icon-96.png',
vibrate: [200, 100, 200],
data: {
url: data.url || '/'
},
actions: data.actions || [
{
action: 'view',
title: '查看'
},
{
action: 'close',
title: '关闭'
}
]
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
// 处理通知点击
self.addEventListener('notificationclick', event => {
event.notification.close();
if (event.action === 'close') {
return;
}
event.waitUntil(
clients.matchAll({ type: 'window' })
.then(clientList => {
// 如果有打开的窗口,聚焦它
for (const client of clientList) {
if (client.url === event.notification.data.url && 'focus' in client) {
return client.focus();
}
}
// 否则打开新窗口
if (clients.openWindow) {
return clients.openWindow(event.notification.data.url);
}
})
);
});
PWA最佳实践
- 渐进增强:确保基础功能在不支持PWA的浏览器中也能工作
- 快速加载:应用外壳应在1秒内加载完成
- 离线优先:设计应用在离线状态下也能提供基本功能
- 响应式设计:确保在所有设备上都有良好的体验
- 定期更新:通过Service Worker自动更新缓存
- 性能监控:使用Lighthouse等工具评估PWA质量
- 用户引导:引导用户将应用添加到主屏幕
Lighthouse性能测试
# 安装Lighthouse
npm install -g lighthouse
# 运行测试
lighthouse https://your-pwa-app.com --output html --output-path ./report.html
# 或者使用Chrome DevTools
# 1. 打开Chrome DevTools
# 2. 转到Lighthouse标签页
# 3. 选择PWA审计类别
# 4. 点击"生成报告"
PWA技术让Web应用具备了原生应用的体验,同时保持了Web的开放性和可访问性。通过合理使用Service Worker、Web App Manifest和推送通知等特性,可以创建出功能丰富、性能优异的渐进式Web应用。