让网页拥有App体验?PWA 将网页变为桌面应用的保姆级教程

1. 前言

PWA,即渐进式网络应用(Progressive Web App),是一种利用现代 Web 技术构建的应用程序,它融合了网页应用和原生应用的优点,能够为用户提供类似于原生应用的体验。PWA 具备以下显著特点:

  1. 渐进增强:可以根据不同浏览器和设备的能力,逐步增强功能,适配各种环境和用户需求。
  2. 响应式布局:能自适应不同设备的屏幕大小和分辨率,确保在桌面、移动设备和平板电脑等各种终端上都有良好的显示效果。
  3. 离线访问:借助 Service Worker 技术,将应用的关键资源缓存到本地,使用户在没有网络连接时也能访问部分功能和内容。
  4. 推送通知:具备推送通知功能,可向用户实时发送新内容、交互提示或重要信息等,保持与用户的互动。
  5. 安装性:用户能够将应用添加到设备主屏幕,实现快速访问,就像使用原生应用一样便捷。
  6. 链接分享:通过 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中获取到的:

  1. 在 body 标签内添加如下代码:
html 复制代码
<body oncontextmenu="return false" ondragstart="return false">
	<!-- 已有代码...... -->
	<script defer src="/register-sw.js"></script>
</body>
  1. 编写 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 文件。如果注册成功,会在控制台输出相关信息;如果注册失败,也会在控制台打印错误信息,方便调试。

  1. 编写 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 添加到桌面了。具体操作步骤因浏览器和设备而异:

  1. 手动添加:
  • 谷歌内核浏览器

    • 在电脑浏览器中打开网页

    • 点击浏览器地址栏右侧的 "+" 图标(如果网页满足 PWA 条件,该图标会显示),或者点击浏览器菜单(通常是三个点的图标),选择 "安装 [应用名称]" 选项。

    • 根据提示完成安装,应用图标将出现在桌面。

    • 或手机浏览器中打开网页

    • 在手机浏览器中,点击浏览器菜单(通常是三个点的图标),选择 "添加到主屏幕" 选项。

    • 用户可以自定义应用名称,然后点击 "添加",应用图标将添加到手机主屏幕。

  • iOS Safari浏览器

    • 在手机浏览器中打开网页
    • 点击浏览器底部的分享按钮(一个向上箭头的图标)。
    • 在弹出的分享菜单中,选择 "添加到主屏幕" 选项。
    • 可以对应用名称进行编辑,然后点击 "添加",应用图标将被添加到主屏幕。
  1. 自动添加(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 的功能和性能,例如优化缓存策略、实现推送通知功能等,为用户提供更优质的服务。

本文参考文档:


如果看了觉得有帮助的,我是@鹏多多,欢迎 点赞 关注 评论;

往期文章

个人主页

相关推荐
DFT计算杂谈4 分钟前
AMSET 设置多核并行计算
java·前端·css·html·css3
花椒技术8 分钟前
AI 协同开发落地复盘:1 小时生成首版后,为什么 Review 和修正又花了 2-3 天
前端·人工智能·架构
万少42 分钟前
万少用9个AI工具,帮朋友完成了一个"不可能"的项目
前端
小小小小宇44 分钟前
Vue `import` 为什么可以异步加载
前端
WMYeah1 小时前
【无标题】
前端·rust·抽奖程序·跨平台抽奖程序
Unbelievabletobe1 小时前
免费外汇api的响应时间在不同时段下的波动分析
大数据·开发语言·前端·python
大哥,带带弟弟1 小时前
Grafana 前端嵌入与 JWT 鉴权实战
前端·grafana
小小小小宇1 小时前
前端 V8 引擎垃圾回收机制与内存问题排查
前端
前端老石人1 小时前
CSS 值定义语法
前端·css
sheeta19981 小时前
Vue 前端基础笔记
前端·vue.js·笔记