本文详解2026年PWA回归趋势,包括离线优先、原生体验、推送通知等实战。
什么是PWA
PWA(Progressive Web Apps):
- 使用Web技术(HTML/CSS/JS)
- 可安装(像原生App)
- 离线工作(Service Worker)
- 推送通知(类似原生App)
vs 原生App:
- 原生App:需要下载(50-200MB)、审核(1-7天)
- PWA:即点即开(< 1MB)、无需审核
2026年趋势:
- PWA回归(Apple允许PWA推送通知)
- 支持率:Chrome 100%、Safari 100%、Firefox 100%
为什么PWA回归(2026)
1. Apple政策变化(2025年):
- 允许PWA推送通知(iOS 17+)
- 允许PWA访问相机/麦克风
- 性能提升(接近原生)
- 用户习惯变化:
- 不愿下载App(存储空间有限)
- 偏好即点即开(PWA)
- 开发者成本:
- 原生App:iOS + Android(2套代码)
- PWA:一套代码(iOS + Android + 桌面)
实战一:创建PWA(基础)
创建Web App Manifest
// public/manifest.json
{
"name": "我的PWA应用",
"short_name": "MyPWA",
"description": "2026年PWA回归实战",
"start_url": "/",
"display": "standalone", // 独立窗口(像原生App)
"background_color": "#ffffff",
"theme_color": "#007bff",
"orientation": "portrait-primary", // 竖屏
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"screenshots": [
{
"src": "/screenshots/screenshot1.png",
"sizes": "1280x720",
"type": "image/png"
}
],
"categories": ["productivity", "utilities"]
}
注册Service Worker
// main.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').then((registration) => {
console.log('Service Worker 注册成功:', registration);
}).catch((error) => {
console.log('Service Worker 注册失败:', error);
});
});
}
添加标签(HTML)
我的PWA应用
实战二:编写Service Worker(离线缓存)
缓存策略一:Cache First(缓存优先)
// sw.js
const CACHE_NAME = 'my-pwa-cache-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js',
'/images/logo.png',
];
// 安装事件:缓存静态资源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
console.log('缓存已打开');
return cache.addAll(urlsToCache);
})
);
});
// 拦截请求:缓存优先策略
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// 缓存命中:返回缓存
if (response) {
return response;
}
// 缓存未命中:网络请求,并缓存
return fetch(event.request).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;
});
})
);
});
// 激活事件:清理旧缓存
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);
}
})
);
})
);
});
缓存策略二:Network First(网络优先)
// sw.js(网络优先,适合动态内容)
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request).then((response) => {
// 网络成功:缓存并返回
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
}).catch(() => {
// 网络失败:返回缓存
return caches.match(event.request);
})
);
});
缓存策略三:Stale-While-Revalidate(过期同时重新验证)
// sw.js(兼顾速度与新鲜度)
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open(CACHE_NAME).then((cache) => {
return cache.match(event.request).then((cachedResponse) => {
const fetchedResponse = fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
// 立即返回缓存(如果有),同时更新缓存
return cachedResponse || fetchedResponse;
});
})
);
});
实战三:推送通知(Push API)
请求通知权限
// main.js
function requestNotificationPermission() {
if (!('Notification' in window)) {
console.log('此浏览器不支持通知');
return;
}
Notification.requestPermission().then((permission) => {
if (permission === 'granted') {
console.log('通知权限已授予');
subscribeToPush();
}
});
}
// 订阅推送
function subscribeToPush() {
navigator.serviceWorker.ready.then((registration) => {
registration.pushManager.subscribe({
userVisibleOnly: true, // 必须显示通知
applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY')
}).then((subscription) => {
console.log('推送订阅成功:', subscription);
// 发送订阅到后端(保存)
sendSubscriptionToServer(subscription);
});
});
}
后端发送推送(Node.js)
// server.js
const webpush = require('web-push');
// 设置VAPID密钥
webpush.setVapidDetails(
'mailto:your-email@example.com',
'YOUR_PUBLIC_VAPID_KEY',
'YOUR_PRIVATE_VAPID_KEY'
);
// 发送推送
app.post('/send-push', (req, res) => {
const subscription = req.body.subscription; // 从数据库获取
const payload = JSON.stringify({
title: '新消息',
body: '您有一条新消息',
icon: '/icons/icon-192x192.png',
tag: 'message',
});
webpush.sendNotification(subscription, payload).then(() => {
res.json({ success: true });
}).catch((error) => {
console.error('推送失败:', error);
res.json({ success: false });
});
});
Service Worker接收推送
// sw.js
self.addEventListener('push', (event) => {
const data = event.data.json();
const options = {
body: data.body,
icon: data.icon,
tag: data.tag,
actions: [
{ action: 'open', title: '打开' },
{ action: 'close', title: '关闭' },
],
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
// 处理通知点击
self.addEventListener('notificationclick', (event) => {
event.notification.close();
if (event.action === 'open') {
clients.openWindow('/messages');
}
});
实战四:添加到主屏幕(A2HS)
引导用户安装
// main.js
let deferredPrompt;
// 监听beforeinstallprompt事件
window.addEventListener('beforeinstallprompt', (event) => {
// 阻止默认行为(不自动显示安装提示)
event.preventDefault();
// 保存事件
deferredPrompt = event;
// 显示自定义安装按钮
showInstallButton();
});
// 显示安装按钮
function showInstallButton() {
const installButton = document.getElementById('install-button');
installButton.style.display = 'block';
installButton.addEventListener('click', () => {
// 隐藏按钮
installButton.style.display = 'none';
// 显示安装提示
deferredPrompt.prompt();
// 等待用户选择
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('用户接受了安装');
} else {
console.log('用户拒绝了安装');
}
deferredPrompt = null;
});
});
}
检测是否已安装
// main.js
window.addEventListener('appinstalled', (event) => {
console.log('PWA已安装');
// 隐藏安装按钮
hideInstallButton();
});
// 检测是否在PWA模式(独立窗口)
if (window.matchMedia('(display-mode: standalone)').matches) {
console.log('在PWA独立模式中');
} else {
console.log('在浏览器中');
}
实战五:后台同步(Background Sync)
注册后台同步
// main.js
if ('serviceWorker' in navigator && 'SyncManager' in window) {
navigator.serviceWorker.ready.then((registration) => {
// 注册后台同步
document.getElementById('submit-form').addEventListener('click', () => {
// 先保存到IndexedDB
saveToIndexedDB({ name: 'John', email: 'john@example.com' });
// 注册同步事件
registration.sync.register('sync-form-data').then(() => {
console.log('后台同步已注册');
});
});
});
}
Service Worker处理后台同步
// sw.js
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-form-data') {
event.waitUntil(
// 从IndexedDB读取数据
readFromIndexedDB().then((data) => {
// 发送到服务器
return fetch('/api/submit-form', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
})
);
}
});
性能优化
优化一:预缓存(Precaching)
// sw.js(Workbox库简化)
import { precacheAndRoute } from 'workbox-precaching';
// 预缓存(构建时生成的文件列表)
precacheAndRoute(self.__WB_MANIFEST);
优化二:运行时缓存(Runtime Caching)
// sw.js(Workbox库)
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst } from 'workbox-strategies';
// 图片:缓存优先
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images-cache',
plugins: [
{ expire: 30 * 24 * 60 * 60 }, // 30天过期
],
})
);
// API:网络优先
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({
cacheName: 'api-cache',
networkTimeoutSeconds: 5, // 5秒超时,则使用缓存
})
);
决策建议
| 场景 | 是否使用PWA | 理由 |
|------|----------------|------|
| 内容型网站(博客/新闻) | ✅ 强烈推荐 | 离线阅读,推送通知 |
| 电商网站 | ✅ 推荐 | 添加到主屏幕,提升转化 |
| 工具型应用(Todo/笔记) | ✅ 推荐 | 离线工作,原生体验 |
| 高性能游戏 | ❌ 不推荐 | WebGL性能不如原生 |
总结
PWA在2026年回归,Apple政策支持,用户体验接近原生。离线优先、推送通知、添加到主屏幕,是PWA的三大核心功能。
立即行动:用Workbox库创建你的第一个PWA!

评论(0)