跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端

前端 PWA 开发指南:构建离线可安装 Web 应用

介绍前端 PWA 技术,阐述其离线访问、主屏幕安装及推送通知等优势。对比了不完整与完整的 PWA 配置示例,详细解析了 manifest.json 配置、Service Worker 缓存策略及推送通知处理逻辑。旨在帮助开发者构建具备原生体验的 Web 应用,强调 PWA 是现代 Web 开发的重要方向而非噱头。

赛博朋克发布于 2026/4/6更新于 2026/10/24114 浏览

前端 PWA 开发指南:构建离线可安装 Web 应用

常见误区

前端 PWA?这不是噱头吗?

"PWA 有什么用,用户直接用浏览器不就好了"——结果用户体验差,无法离线访问; "我有原生 App,不需要 PWA"——结果开发成本高,维护困难; "PWA 就是加个 manifest 和 service worker,多简单"——结果功能不完整,用户体验差。

需明确,PWA 不是简单的技术组合,而是一种现代化的 Web 应用模式!

为什么你需要这个?

  • 离线访问:即使没有网络也能访问应用
  • 安装到主屏幕:像原生 App 一样方便使用
  • 推送通知:及时向用户发送重要信息
  • 性能提升:缓存静态资源,加快加载速度
  • 跨平台:一次开发,多平台运行

反面教材

<!-- 反面教材:不完整的 PWA 配置 -->
<!DOCTYPE html>
<html>
<head>
<title>我的 PWA 应用</title>
<!-- 缺少 manifest.json -->
<!-- 缺少 service worker -->
</head>
<body>
<h1>我的 PWA 应用</h1>
<p>这是一个 PWA 应用</p>
</body>
</html>
// 反面教材:简单的 service worker
// service-worker.js
self.addEventListener('install', event => {
console.log('Service Worker 安装');
});
self.addEventListener('activate', event => {
console.log('Service Worker 激活');
});
self.addEventListener('fetch', event => {
console.log('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>我的 PWA 应用</title>
<!-- 添加 manifest.json -->
<link rel="manifest" href="/manifest.json">
<!-- 添加主题颜色 -->
<meta name="theme-color" content="#317EFB">
<!-- 添加 Apple Touch Icon -->
<link rel="apple-touch-icon" href="/icons/icon-192x192.png">
<!-- 添加预加载 -->
<link rel="preload" href="/style.css" as="style">
<link rel="preload" href="/script.js" as="script">
<link rel="stylesheet" href="/style.css">
</head>
<body>
<h1>我的 PWA 应用</h1>
<p>这是一个完整的 PWA 应用</p>
<button>发送通知</button>
<script src="/script.js"></script>
<script>
// 注册 service worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker 注册成功:', registration.scope);
})
.catch(error => {
console.log('Service Worker 注册失败:', error);
});
});
}
</script>
</body>
</html>
// 正确的做法:manifest.json
{
"name": "我的 PWA 应用",
"short_name": "PWA 应用",
"description": "一个完整的 PWA 应用示例",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#317EFB",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
}
]
}
// 正确的做法:完整的 service worker
// service-worker.js
const CACHE_NAME = 'pwa-app-cache-v1';
const ASSETS_TO_CACHE = [
'/','/index.html','/style.css','/script.js','/manifest.json',
'/icons/icon-72x72.png','/icons/icon-96x96.png','/icons/icon-128x128.png',
'/icons/icon-144x144.png','/icons/icon-152x152.png','/icons/icon-192x192.png',
'/icons/icon-384x384.png','/icons/icon-512x512.png'
];

// 安装 Service Worker
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Cache opened');
return cache.addAll(ASSETS_TO_CACHE);
})
.then(() => self.skipWaiting())
);
});

// 激活 Service Worker
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
)
.then(() => 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') {
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
}
return response;
})
.catch(error => {
// 网络错误时返回离线页面
if (event.request.mode === 'navigate') {
return caches.match('/offline.html');
}
});
})
);
});

// 处理推送通知
self.addEventListener('push', event => {
const data = event.data.json();
const options = {
body: data.body,
icon: '/icons/icon-192x192.png',
badge: '/icons/icon-72x72.png',
data: { url: data.url }
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});

// 处理通知点击
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
});
// 正确的做法:使用推送通知
// script.js

// 请求通知权限
function requestNotificationPermission() {
if ('Notification' in window) {
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
console.log('通知权限已授予');
} else {
console.log('通知权限被拒绝');
}
});
}
}

// 发送通知
function sendNotification() {
if ('serviceWorker' in navigator && 'PushManager' in window) {
navigator.serviceWorker.ready.then(registration => {
registration.showNotification('测试通知', {
body: '这是一条测试通知',
icon: '/icons/icon-192x192.png',
badge: '/icons/icon-72x72.png',
data: { url: '/' }
});
});
}
}

// 页面加载时请求通知权限
window.addEventListener('load', () => {
requestNotificationPermission();
// 绑定按钮点击事件
const notificationBtn = document.getElementById('notification-btn');
if (notificationBtn) {
notificationBtn.addEventListener('click', sendNotification);
}
});

// 检查是否是 PWA 模式
function checkPwaMode() {
const isPwa = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone || document.referrer.includes('android-app://');
if (isPwa) {
console.log('应用以 PWA 模式运行');
} else {
console.log('应用以浏览器模式运行');
}
}
checkPwaMode();

核心观点

这才是前端 PWA!不是简单地添加 manifest 和 service worker,而是构建一个完整的 PWA 应用,包括离线访问、推送通知、安装到主屏幕等功能。

记住,PWA 不是替代原生 App,而是一种补充。它可以让你的 Web 应用获得类似原生 App 的体验,同时保持 Web 的跨平台优势。

所以,别再觉得 PWA 是噱头了,它是现代 Web 开发的重要方向!

总结

  • manifest.json:配置应用名称、图标、显示模式等
  • Service Worker:实现离线缓存、推送通知等功能
  • 离线访问:缓存静态资源,确保无网络时也能访问
  • 安装到主屏幕:提供类似原生 App 的入口
  • 推送通知:及时向用户发送重要信息
  • 性能优化:缓存策略、预加载等
  • 可访问性:确保 PWA 对所有用户都友好
  • 跨平台:一次开发,多平台运行

PWA,让你的网站变成真正的应用!

目录

  1. 前端 PWA 开发指南:构建离线可安装 Web 应用
  2. 常见误区
  3. 为什么你需要这个?
  4. 反面教材
  5. 正确的做法
  6. 核心观点
  7. 总结

更多推荐文章

查看全部
  • 区块链 WEB3 时间长河共识算法(Time River Consensus Algorithm)
  • Spring Boot RESTful API 开发与测试
  • 2026 年 5 月无人机新规实施:实名登记与激活双重要求详解
  • GitHub Copilot 在 Web 开发中的 20 个高效用法
  • local-dream 项目实现安卓端 Stable Diffusion 高效运行与骁龙 NPU 加速
  • 从敏捷到生成式:AIGC 重塑软件测试全流程
  • 低代码开发:企业应用搭建的高效方案
  • Trae Agent 结合 MCP 实现 Gitee 自动化辅助工具探索
  • 全国计算机等级考试二级证书的作用与含金量分析
  • ESP32 无人机远程识别:ArduRemoteID 配置教程
  • Trae IDE 接入即梦 AI 生成高质量图片
  • 网络安全渗透测试常用术语详解:50 个核心概念解析
  • 自然语言处理在金融风控中的实战应用
  • YOLO11 基于 DroneVehicle 数据集的无人机车辆检测实战
  • C++ 继承机制详解:从基础到虚拟继承
  • Ubuntu 22.04 LTS 物理机安装与配置避坑指南
  • RK3568 嵌入式开发环境搭建:Linux 5.10 内核移植与驱动适配
  • 内网部署本地大模型:Llama 与 Qwen 安全实战指南
  • UV 国内源配置指南:PyPI 与解释器加速实战
  • 2026 年 Python AI 开源工具精选:Agent 与大模型推理实战

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online