PWA(Progressive Web App)是一种Web应用,使用现代Web技术提供类似原生应用的体验。
东巴文(db-w.cn) 认为:PWA是Web应用的未来,它结合了Web和原生应用的优势,提供了离线工作、推送通知、添加到主屏幕等功能,是现代Web开发的重要方向。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PWA概述 - 东巴文</title>
<style>
body {
font-family: 'Segoe UI', sans-serif;
padding: 20px;
background: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 10px;
}
h1 {
color: #667eea;
margin-bottom: 20px;
}
h2 {
color: #764ba2;
margin: 30px 0 15px;
border-left: 4px solid #764ba2;
padding-left: 15px;
}
.pwa-demo {
background: #f8f9fa;
padding: 20px;
border: 2px solid #ddd;
margin: 15px 0;
border-radius: 10px;
}
.pwa-demo h3 {
color: #667eea;
margin-bottom: 10px;
}
.feature-list {
list-style: none;
padding: 0;
}
.feature-list li {
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.feature-list li:before {
content: "✓ ";
color: #667eea;
font-weight: bold;
}
.feature-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
.feature-table th,
.feature-table td {
padding: 12px;
text-align: left;
border: 1px solid #ddd;
}
.feature-table th {
background: #667eea;
color: white;
}
</style>
</head>
<body>
<div class="container">
<h1>PWA概述</h1>
<h2>什么是PWA</h2>
<div class="pwa-demo">
<h3>核心概念</h3>
<p>PWA(Progressive Web App)是一种使用现代Web技术构建的应用,提供类似原生应用的体验。它具有以下特点:</p>
<ul class="feature-list">
<li><strong>渐进式:</strong> 适用于所有浏览器,渐进增强用户体验</li>
<li><strong>响应式:</strong> 适合任何设备:桌面、手机、平板</li>
<li><strong>离线工作:</strong> 使用Service Worker支持离线功能</li>
<li><strong>类原生应用:</strong> 感觉像原生应用,交互流畅</li>
<li><strong>安全:</strong> 通过HTTPS提供服务,确保安全</li>
<li><strong>可发现:</strong> 可被搜索引擎识别,SEO友好</li>
<li><strong>可安装:</strong> 可添加到主屏幕,无需应用商店</li>
<li><strong>可链接:</strong> 通过URL分享,无需安装</li>
</ul>
</div>
<h2>PWA核心功能</h2>
<table class="feature-table">
<thead>
<tr>
<th>功能</th>
<th>技术</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>离线工作</strong></td>
<td>Service Worker</td>
<td>缓存资源,离线访问</td>
</tr>
<tr>
<td><strong>推送通知</strong></td>
<td>Push API</td>
<td>推送消息,提高用户参与度</td>
</tr>
<tr>
<td><strong>后台同步</strong></td>
<td>Background Sync API</td>
<td>后台同步数据</td>
</tr>
<tr>
<td><strong>添加到主屏幕</strong></td>
<td>Web App Manifest</td>
<td>安装应用,全屏运行</td>
</tr>
<tr>
<td><strong>支付</strong></td>
<td>Payment Request API</td>
<td>原生支付体验</td>
</tr>
<tr>
<td><strong>凭证管理</strong></td>
<td>Credential Management API</td>
<td>自动登录</td>
</tr>
</tbody>
</table>
<h2>PWA优势</h2>
<div class="pwa-demo">
<h3>与传统Web应用对比</h3>
<div style="background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 5px; margin: 10px 0;">
<pre>
传统Web应用:
- 需要网络连接
- 无法离线访问
- 无法推送通知
- 无法添加到主屏幕
- 加载速度慢
- 用户体验差
PWA:
- 支持离线工作
- 可推送通知
- 可添加到主屏幕
- 加载速度快
- 类原生应用体验
- SEO友好
- 跨平台
- 无需应用商店审核
</pre>
</div>
</div>
<h2>PWA示例</h2>
<div class="pwa-demo">
<h3>基本结构</h3>
<div style="background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 5px; margin: 10px 0;">
<pre>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- PWA Meta标签 -->
<meta name="theme-color" content="#667eea">
<meta name="description" content="我的PWA应用">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="我的PWA">
<!-- Web App Manifest -->
<link rel="manifest" href="/manifest.json">
<!-- 图标 -->
<link rel="icon" type="image/png" href="/icons/icon-192.png">
<link rel="apple-touch-icon" href="/icons/icon-192.png">
<title>我的PWA</title>
</head>
<body>
<!-- 应用内容 -->
<script>
// 注册Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker注册成功:', registration.scope);
})
.catch(error => {
console.error('Service Worker注册失败:', error);
});
});
}
</script>
</body>
</html>
</pre>
</div>
</div>
<div style="background: #fff3cd; padding: 15px; border-radius: 5px; margin-top: 20px;">
<strong>东巴文提示:</strong>PWA是现代Web应用的重要方向。掌握Service Worker、Web App Manifest、Push API等核心技术,能构建出体验优秀的渐进式Web应用,提供类原生应用的用户体验。
</div>
</div>
</body>
</html>
Service Worker是PWA的核心,提供了离线缓存、推送通知、后台同步等功能。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Service Worker - 东巴文</title>
<style>
body {
font-family: 'Segoe UI', sans-serif;
padding: 20px;
background: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 10px;
}
h1 {
color: #667eea;
margin-bottom: 20px;
}
h2 {
color: #764ba2;
margin: 30px 0 15px;
border-left: 4px solid #764ba2;
padding-left: 15px;
}
.sw-demo {
background: #f8f9fa;
padding: 20px;
border: 2px solid #ddd;
margin: 15px 0;
border-radius: 10px;
}
.sw-demo h3 {
color: #667eea;
margin-bottom: 10px;
}
.code-block {
background: #2d2d2d;
color: #f8f8f2;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
margin: 10px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>Service Worker</h1>
<h2>注册Service Worker</h2>
<div class="sw-demo">
<h3>注册代码</h3>
<div class="code-block">
<pre>
// 检查浏览器支持
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker注册成功:', registration.scope);
// 检查更新
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// 新版本可用
console.log('新版本可用,请刷新页面');
}
});
});
})
.catch(error => {
console.error('Service Worker注册失败:', error);
});
});
// 监听Service Worker更新
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('Service Worker已更新');
});
}
</pre>
</div>
</div>
<h2>Service Worker生命周期</h2>
<div class="sw-demo">
<h3>生命周期事件</h3>
<div class="code-block">
<pre>
// sw.js - Service Worker文件
const CACHE_NAME = 'my-pwa-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js',
'/images/logo.png'
];
// 安装事件
self.addEventListener('install', event => {
console.log('Service Worker安装中...');
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('缓存已打开');
return cache.addAll(urlsToCache);
})
.then(() => {
console.log('所有资源已缓存');
return self.skipWaiting(); // 跳过等待,立即激活
})
);
});
// 激活事件
self.addEventListener('activate', event => {
console.log('Service Worker激活中...');
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
console.log('删除旧缓存:', cacheName);
return caches.delete(cacheName);
}
})
);
}).then(() => {
console.log('Service Worker已激活');
return self.clients.claim(); // 立即控制所有客户端
})
);
});
// 拦截请求
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;
});
})
);
});
</pre>
</div>
</div>
<h2>缓存策略</h2>
<div class="sw-demo">
<h3>常用缓存策略</h3>
<div class="code-block">
<pre>
// 1. 缓存优先(Cache First)
// 适用于不常变化的静态资源
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
);
});
// 2. 网络优先(Network First)
// 适用于需要最新数据的动态内容
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => {
// 缓存响应
const responseClone = response.clone();
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
// 网络失败,返回缓存
return caches.match(event.request);
})
);
});
// 3. 网络优先,缓存降级(Stale While Revalidate)
// 快速响应,后台更新
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
const fetchPromise = fetch(event.request).then(networkResponse => {
// 更新缓存
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
});
// 返回缓存或网络响应
return cachedResponse || fetchPromise;
})
);
});
// 4. 仅缓存(Cache Only)
// 适用于完全静态的资源
self.addEventListener('fetch', event => {
event.respondWith(caches.match(event.request));
});
// 5. 仅网络(Network Only)
// 适用于必须实时的数据
self.addEventListener('fetch', event => {
event.respondWith(fetch(event.request));
});
</pre>
</div>
</div>
<h2>离线页面</h2>
<div class="sw-demo">
<h3>离线回退</h3>
<div class="code-block">
<pre>
// 缓存离线页面
const OFFLINE_URL = '/offline.html';
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll([
'/',
OFFLINE_URL
]);
})
);
});
// 拦截导航请求
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request)
.catch(() => {
return caches.match(OFFLINE_URL);
})
);
}
});
// offline.html
/*
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>离线 - 我的PWA</title>
<style>
body {
font-family: 'Segoe UI', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: #f5f5f5;
}
.offline {
text-align: center;
padding: 40px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #667eea;
margin-bottom: 10px;
}
p {
color: #666;
margin-bottom: 20px;
}
button {
padding: 10px 20px;
background: #667eea;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="offline">
<h1>你离线了</h1>
<p>请检查网络连接后重试</p>
<button onclick="location.reload()">重试</button>
</div>
</body>
</html>
*/
</pre>
</div>
</div>
<div style="background: #fff3cd; padding: 15px; border-radius: 5px; margin-top: 20px;">
<strong>东巴文提示:</strong>Service Worker是PWA的核心技术,提供了离线缓存、请求拦截、推送通知等功能。掌握生命周期、缓存策略、离线处理,能构建出体验优秀的PWA应用。
</div>
</div>
</body>
</html>
Web App Manifest是一个JSON文件,描述了PWA的元数据,如名称、图标、启动画面等。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web App Manifest - 东巴文</title>
<style>
body {
font-family: 'Segoe UI', sans-serif;
padding: 20px;
background: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 10px;
}
h1 {
color: #667eea;
margin-bottom: 20px;
}
h2 {
color: #764ba2;
margin: 30px 0 15px;
border-left: 4px solid #764ba2;
padding-left: 15px;
}
.manifest-demo {
background: #f8f9fa;
padding: 20px;
border: 2px solid #ddd;
margin: 15px 0;
border-radius: 10px;
}
.manifest-demo h3 {
color: #667eea;
margin-bottom: 10px;
}
.code-block {
background: #2d2d2d;
color: #f8f8f2;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
margin: 10px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>Web App Manifest</h1>
<h2>Manifest文件</h2>
<div class="manifest-demo">
<h3>manifest.json</h3>
<div class="code-block">
<pre>
{
"name": "我的PWA应用",
"short_name": "我的PWA",
"description": "这是一个渐进式Web应用示例",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#667eea",
"orientation": "portrait-primary",
"scope": "/",
"lang": "zh-CN",
"dir": "ltr",
"categories": ["productivity", "utilities"],
"icons": [
{
"src": "/icons/icon-72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/icons/icon-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "/icons/icon-144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/icons/icon-152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"screenshots": [
{
"src": "/screenshots/screenshot1.png",
"sizes": "1280x720",
"type": "image/png"
}
],
"shortcuts": [
{
"name": "新建",
"short_name": "新建",
"description": "创建新内容",
"url": "/new",
"icons": [
{
"src": "/icons/new.png",
"sizes": "96x96"
}
]
},
{
"name": "搜索",
"short_name": "搜索",
"description": "搜索内容",
"url": "/search",
"icons": [
{
"src": "/icons/search.png",
"sizes": "96x96"
}
]
}
],
"related_applications": [
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=com.example.app"
}
],
"prefer_related_applications": false
}
</pre>
</div>
</div>
<h2>Manifest属性</h2>
<div class="manifest-demo">
<h3>主要属性说明</h3>
<div class="code-block">
<pre>
// 基本属性
{
// 应用名称
"name": "我的PWA应用", // 完整名称
"short_name": "我的PWA", // 短名称,显示在主屏幕
// 描述
"description": "应用描述",
// 启动URL
"start_url": "/", // 启动时加载的URL
"scope": "/", // 应用范围
// 显示模式
"display": "standalone", // fullscreen | standalone | minimal-ui | browser
// 方向
"orientation": "portrait-primary", // portrait | landscape | any
// 主题颜色
"theme_color": "#667eea", // 主题颜色
"background_color": "#ffffff", // 启动画面背景色
// 语言
"lang": "zh-CN",
"dir": "ltr" // ltr | rtl
}
// 显示模式说明
/*
fullscreen: 全屏模式,隐藏所有浏览器UI
standalone: 独立应用模式,像原生应用
minimal-ui: 最小UI模式,保留基本浏览器控件
browser: 浏览器模式,传统网页
*/
// 图标要求
/*
必须提供:
- 192x192: Android Chrome要求
- 512x512: Android Chrome要求
- 180x180: iOS要求
推荐提供:
- 72x72, 96x96, 128x128, 144x144, 152x152, 384x384
purpose:
- any: 任何用途
- maskable: 可遮罩图标(Android自适应图标)
*/
</pre>
</div>
</div>
<h2>在HTML中引用</h2>
<div class="manifest-demo">
<h3>HTML配置</h3>
<div class="code-block">
<pre>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引用Manifest -->
<link rel="manifest" href="/manifest.json">
<!-- 主题颜色 -->
<meta name="theme-color" content="#667eea">
<!-- iOS支持 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="我的PWA">
<!-- iOS图标 -->
<link rel="apple-touch-icon" href="/icons/icon-192.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-180.png">
<link rel="apple-touch-icon" sizes="167x167" href="/icons/icon-167.png">
<!-- Windows Tiles -->
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
<meta name="msapplication-TileColor" content="#667eea">
<title>我的PWA</title>
</head>
<body>
<!-- 应用内容 -->
</body>
</html>
</pre>
</div>
</div>
<h2>安装提示</h2>
<div class="manifest-demo">
<h3>自定义安装提示</h3>
<div class="code-block">
<pre>
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
// 阻止默认提示
e.preventDefault();
// 保存事件
deferredPrompt = e;
// 显示自定义安装按钮
showInstallButton();
});
function showInstallButton() {
const installButton = document.createElement('button');
installButton.textContent = '安装应用';
installButton.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
padding: 15px 30px;
background: #667eea;
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
z-index: 1000;
`;
installButton.addEventListener('click', async () => {
// 显示安装提示
deferredPrompt.prompt();
// 等待用户响应
const { outcome } = await deferredPrompt.userChoice;
console.log(`用户选择: ${outcome}`);
// 清除引用
deferredPrompt = null;
// 移除按钮
installButton.remove();
});
document.body.appendChild(installButton);
}
// 监听安装成功事件
window.addEventListener('appinstalled', (e) => {
console.log('应用已安装');
});
</pre>
</div>
</div>
<div style="background: #fff3cd; padding: 15px; border-radius: 5px; margin-top: 20px;">
<strong>东巴文提示:</strong>Web App Manifest是PWA的重要组成部分,定义了应用的元数据和安装信息。正确配置manifest.json,能提供良好的安装体验和启动画面,让PWA更像原生应用。
</div>
</div>
</body>
</html>
完成本章学习后,请尝试回答以下问题:
选择题: PWA的核心技术是什么?
填空题: Service Worker的生命周期包括____、____和____三个阶段。
简答题: PWA相比传统Web应用有哪些优势?
实践题: 创建一个简单的PWA应用,要求:
应用题: 分析一个知名PWA应用(如Twitter Lite、Starbucks),总结其PWA特性实现方式。