1. 前言
PWA,即渐进式网络应用(Progressive Web App),是一种利用现代 Web 技术构建的应用程序,它融合了网页应用和原生应用的优点,能够为用户提供类似于原生应用的体验。PWA 具备以下显著特点:
- 渐进增强:可以根据不同浏览器和设备的能力,逐步增强功能,适配各种环境和用户需求。
- 响应式布局:能自适应不同设备的屏幕大小和分辨率,确保在桌面、移动设备和平板电脑等各种终端上都有良好的显示效果。
- 离线访问:借助 Service Worker 技术,将应用的关键资源缓存到本地,使用户在没有网络连接时也能访问部分功能和内容。
- 推送通知:具备推送通知功能,可向用户实时发送新内容、交互提示或重要信息等,保持与用户的互动。
- 安装性:用户能够将应用添加到设备主屏幕,实现快速访问,就像使用原生应用一样便捷。
- 链接分享:通过 URL 链接即可进行分享和访问,方便用户将应用分享给他人。

2. 准备工作
在开始将网页配置为 PWA 并添加到桌面之前,需要确保满足以下条件:
- 使用 HTTPS 协议:除了本地开发环境(localhost)外,为了保证安全性和兼容性,网页必须使用 HTTPS 协议。因为 Service Worker 只能在安全的上下文环境中运行,这是 PWA 实现离线访问等功能的基础。
- 准备应用图标:准备不同尺寸的应用图标,以适应各种设备和平台的显示需求。通常建议提供 192x192 像素、512x512 像素等常见尺寸的图标,格式一般为 PNG。
3. 配置 manifest.json 文件
manifest.json 文件,用于定义 PWA 的相关信息,包括应用名称、图标、启动页面、显示模式等。这些信息将决定用户将应用添加到桌面后的显示效果和行为。通常都放在项目根目录下面,并且在根 index.html 中引入。其常用配置如下:
json
{
// 应用的全名
"name": "React.App",
// 应用的短名称,当全名对于可用空间来说太长时可以使用它。
"short_name": "R.App",
// 应用描述,当用户在应用商店(Microsoft Store)中查看应用时,这些文本可以帮助他们理解应用的目的。
"description": "一个React的示例App",
// 应用的图标对应不同尺寸
"icons": [
{
"src": "/logo/logo-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/logo/logo-512.png",
"type": "image/png",
"sizes": "512x512"
}
],
// 应用启动时应该打开的URL,这里打开了首页且带了个参数source=pwa
"start_url": "/home?source=pwa",
// 使页面显示效果为沉浸式 没有地址栏和底部状态栏
"display": "standalone",
// 导航界面的背景颜色
"background_color": "#000000",
// 手机最上方显示运营商及电量、时间等这一栏的颜色
"theme_color": "#000000",
// 应用方向 竖向:portrait 横向:landscape
"orientation": "portrait",
// 用于网站的首选显示模式 默认全屏 否则是独立的应用程序
"display_override": ["fullscreen", "minimal-ui"]
}
更多详细配置,可参考:官方文档
然后在项目的 index.html 文件的标签内,添加如下代码,将 manifest.json 文件与网页关联起来
html
<!-- 网页清单 定义应用的元数据(如名称、图标、启动 URL 等) -->
<link rel="manifest" href="/manifest.json" />
<!-- iOS的网页清单 定义应用的元数据(如名称、图标、启动 URL 等) -->
<link rel="manifest" href="/manifest.webmanifest" />
注意:iOS 的 manifest 文件,需要使用.webmanifest 后缀,直接复制一份 manifest.json 文件,并修改后缀为.webmanifest。
4. 配置 Service Worker
Service Worker 是 PWA 的核心技术之一,它允许网页在后台运行脚本,实现离线缓存、拦截网络请求等功能,从而提升应用的性能和用户体验,比如把缓存的资源保存到本地,当用户没有网络连接时,依然可以使用这些资源。如下图,资源来自Service Worker中获取到的:

- 在 body 标签内添加如下代码:
html
<body oncontextmenu="return false" ondragstart="return false">
<!-- 已有代码...... -->
<script defer src="/register-sw.js"></script>
</body>
- 编写 register-sw.js 文件,用于注册 Service Worker,并设置其缓存策略。通常都放在项目根目录下面,并且在 body 标签内引入,如下:
javascript
/**
* @description 执行SW的register
* @description 单独拿出来使用defer避免阻塞
*/
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('./service-worker.js')
.then((registration) => {
console.log('%cService Worker 注册成功:', 'color:green;', registration)
})
.catch((error) => {
console.error('%cService Worker 注册失败:', 'color:red;', error)
})
})
}
上述代码首先检查浏览器是否支持 Service Worker,如果支持,则在页面加载完成后(load 事件触发时)尝试注册名为 service-worker.js 的 Service Worker 文件。如果注册成功,会在控制台输出相关信息;如果注册失败,也会在控制台打印错误信息,方便调试。
- 编写 service-worker.js 文件,用于定义 Service Worker 的行为,比如缓存静态资源、拦截网络请求等。通常都放在项目根目录下面,并且在 body 标签内引入,如下:
javascript
/**
* @description PWA 用于实现离线缓存和后台同步等功能
* @description 安装时缓存静态资源
*/
// 缓存空间名称
const CACHE_NAME = 'test-pwa-cache'
// 缓存静态资源
const urlsToCache = [
'/',
'/index.html',
'favicon.ico',
'/logo/logo-192.png',
'/logo/logo-192.jpg',
'/logo/logo-512.png',
'/logo/logo-512.jpg',
'/video/loading.mp4',
]
// 安装事件 此时缓存静态资源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
console.log('%cService Worker 缓存:', 'color:gray;', cache)
return cache.addAll(urlsToCache)
})
)
})
// 激活事件 清理旧的缓存
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(function (keyList) {
return Promise.all(
keyList
.filter(function (key) {
return key.startsWith('test-pwa-cache-') && key !== CACHE_NAME
})
.map(function (key) {
return caches.delete(key)
})
)
})
)
})
// 拦截网络请求 此时可以判断是否命中缓存内容并返回
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
console.log('%cService Worker 拦截成功,返回缓存内容:', 'color:blue;', response)
return response
}
return fetch(event.request)
})
)
})
上述代码中:
- 安装阶段:install 事件触发时,打开指定的缓存(CACHE_NAME),并将 PRE_CACHE 列表中的资源缓存到本地。任何资源路径出错都可能导致 Service Worker 安装失败。
- 激活阶段:activate 事件触发时,清理旧的缓存。遍历所有缓存名称,删除那些以 cache - 开头且不等于当前缓存名称(CACHE_NAME)的缓存。
- 请求拦截阶段:fetch 事件触发时,拦截所有网络请求。首先尝试从缓存中匹配请求,如果找到匹配的缓存响应,则直接返回;如果没有找到,则发起网络请求。在网络请求成功返回且状态码为 200 时,将响应克隆一份缓存到本地,以便下次可以直接从缓存中获取。
5. 将网页添加到桌面
当完成上述 Web App Manifest 和 Service Worker 的配置后,用户就可以将该网页作为 PWA 添加到桌面了。具体操作步骤因浏览器和设备而异:
- 手动添加:
-
谷歌内核浏览器
-
在电脑浏览器中打开网页
-
点击浏览器地址栏右侧的 "+" 图标(如果网页满足 PWA 条件,该图标会显示),或者点击浏览器菜单(通常是三个点的图标),选择 "安装 [应用名称]" 选项。
-
根据提示完成安装,应用图标将出现在桌面。
-
或手机浏览器中打开网页
-
在手机浏览器中,点击浏览器菜单(通常是三个点的图标),选择 "添加到主屏幕" 选项。
-
用户可以自定义应用名称,然后点击 "添加",应用图标将添加到手机主屏幕。
-
-
iOS Safari浏览器
- 在手机浏览器中打开网页
- 点击浏览器底部的分享按钮(一个向上箭头的图标)。
- 在弹出的分享菜单中,选择 "添加到主屏幕" 选项。
- 可以对应用名称进行编辑,然后点击 "添加",应用图标将被添加到主屏幕。
- 自动添加(iOS不支持):
在页面加载的生命周期中,监听beforeinstallprompt
事件,当事件触发后,阻止掉默认的安装行为,存储在变量中,并显示安装按钮。当用户点击按钮时,释放存储的事件,此时会弹出一个系统的安装对话框,用户可以点击安装应用,具体代码如下:
tsx
// 使用此变量存储事件
const deferredPrompt = useRef<any>(null)
/**
* @description 初始化生命周期
*/
useEffect(() => {
// 监听 PWA安装事件
window.addEventListener('beforeinstallprompt', (e) => {
// 阻止系统自己弹窗
e.preventDefault()
// 保存事件,稍后触发安装
deferredPrompt.current = e
})
}, [])
/**
* @description 判断是否在PWA中打开
*/
const isInStandaloneMode = () => {
const is =
window.matchMedia('(display-mode: standalone)').matches ||
(window.navigator as T_Any).standalone ||
document.referrer.includes('android-app://')
console.log(`${is ? '在' : '不在'}PWA中打开`)
return is
}
/**
* @description 添加到主屏幕
*/
const handleAddPWA = () => {
if (!isInStandaloneMode()) {
if (deferredPrompt.current) {
console.log('%c添加到主屏幕事件:触发成功', 'color: green;')
// 触发PWA安装
deferredPrompt.current.prompt()
// 监听安装结果
deferredPrompt.current.userChoice.then((choiceResult: T_Any) => {
if (choiceResult.outcome === 'accepted') {
console.log('成功!')
} else {
console.log('用户取消安装,该deferredPrompt.prompt仍可继续使用')
}
deferredPrompt.current = null
})
} else {
console.log('%c添加到主屏幕事件:触发失败', 'color: red;')
// 此时打开提示弹出,引导用户手动添加到屏幕
}
} else {
console.log('已经成功添加!')
}
}
上述手动添加的代码,本质上是拦截了系统的通知事件,因此不同浏览器和手机设备可能有不同的实现方式。并且开发者只能被动拦截,无法主动触发,成功率不稳定,所以需要做一个手动安装的引导弹窗,作为替补方案。
通过以上步骤,你可以将普通网页转化为 PWA 应用,并让用户方便地将其添加到桌面。这不仅提升了用户体验,使网页应用更接近原生应用的便捷性,还能增加用户对应用的访问频率和粘性。在实际应用中,你可以根据具体需求进一步优化 PWA 的功能和性能,例如优化缓存策略、实现推送通知功能等,为用户提供更优质的服务。
本文参考文档:
如果看了觉得有帮助的,我是@鹏多多,欢迎 点赞 关注 评论;
往期文章
- 助你上手Vue3全家桶之Vue3教程
- 使用nvm管理node.js版本以及更换npm淘宝镜像源
- 超详细!Vue的十种通信方式
- 手把手教你搭建规范的团队vue项目,包含commitlint,eslint,prettier,husky,commitizen等等
- vue中利用.env文件存储全局环境变量,以及配置vue启动和打包命令
个人主页