本文详解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!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。