让渐进式 Web 应用 (PWA) 更像应用
让您的渐进式 Web 应用不像网站,更接近“真正的”应用
在玩渐进式 Web 应用 (PWA) 流行语字谜游戏时,选“PWA 只是网站”是一个稳赢的选择。微软的 PWA 文档认同这个说法,我们在这个网站上也这么说,甚至提出 PWA 的 Frances Berriman 和 Alex Russell 也是这么写的。是的,PWA 只是网站,但它们也远不止于此。只要实现得当,PWA 不会给人网站的感觉,而是像一个“真正的”应用。那么,像真正的应用意味着什么呢?
为了回答这个问题,让我拿 Apple Podcasts 这个应用举例。它可在桌面版 macOS 和移动设备上的 iOS(还有 iPadOS)上使用。虽然 Podcasts 是媒体应用,但我在本例中阐述的核心思想也适用于其他类别的应用。
可离线运行 #
假设我们后退一步,思考下手机或台式电脑上安装的特定于平台的应用程序,那么能发现一件明显的事:永远不会看不到内容。即使是离线状态的 Podcasts 应用,也会显示一些东西。虽然没有网络,但应用程序仍会自然打开。Top Charts 不显示任何内容,而是展示了现在无法连接消息与重试按钮。虽然我没有受到热烈的欢迎,但依然看到了一些内容。如何在 web 上实现
Podcasts 应用遵循所谓的应用外壳 (app shell) 模型。显示核心应用所需的所有静态内容都缓存在本地,包括左侧菜单图标和核心播放器 UI 图标等装饰性图像。而 Top Charts 等动态内容的数据仅会按需加载;如果加载失败,则会显示本地缓存的后备内容。请查看应用外壳模型一文,了解如何在您的 web 应用使用此架构模型。
可使用离线内容且可播放媒体数据 #
离线时,我依然可以通过左侧抽屉导航到已下载部分并欣赏下载好的播客剧集,应用可以播放这些播客剧集并显示所有元数据(如插图和描述)。如何在 web 上实现
可以从缓存提供以前下载的媒体内容,例如使用 Workbox 库中的提供缓存的音频和视频方法。其他内容可以随时存储在缓存或 IndexedDB 中。请查阅 Web 存储一文了解所有详细信息,以及何时使用何种存储技术。如果您的数据需要持久存储,不想在可用内存变低时被清除,则可以使用 Persistent Storage API 。
后台主动下载 #
当我重新上线时,当然可以使用诸如 http 203
之类的查询来搜索内容,当我决定订阅搜索结果 HTTP 203 播客时,会自动下载该系列的最新一集。如何在 web 上实现
下载播客剧集这项操作可能需要更长时间。您可以通过 Background Fetch API 将下载委托给浏览器后台处理。在 Android 上,浏览器甚至可以将这些下载进一步委托给操作系统,因此不需要持续运行浏览器。下载完成后,应用的服务工作进程将被唤醒,您可以决定如何处理响应。
与其他应用共享和交互 #
Podcasts 应用会自然地与其他应用集成。例如当我右键单击喜欢的剧集时,可以把它分享给设备上的其他程序,比如 Messages 应用。它还自然地与系统剪贴板集成。我可以右键单击任何剧集并复制它的链接。如何在 web 上实现
您可以使用 Web Share API 和 Web Share Target API 将您的应用与设备上的其他应用分享和接收文本、文件和链接。尽管 Web 应用尚无法将菜单项添加到操作系统的内置右键单击菜单中,但仍有许多其他方法可以与设备的其他应用共享链接。通过 Async Clipboard API ,您可以以编程方式将文本和图像数据(PNG 图像)读写到系统剪贴板。在 Android 上,您可以使用 Contact Picker API 从设备的联系人管理器中选择联系人。如果您同时提供平台特定应用和 PWA,那么可以使用 Get Installed Related Apps API 来检查是否安装了平台特定应用;在这种情况下,您不需要鼓励用户安装 PWA 或接受网络推送通知。
后台应用刷新 #
我可以在 Podcasts 应用的设置中配置为自动下载新剧集。这样更新内容会随时自动下载。真神奇。如何在 web 上实现
Periodic Background Sync API 可以让应用在后台定期刷新内容,而无需运行。这意味着可以主动提供新内容,使用户可以在需要时随时畅享。
通过云同步状态 #
同时,我的订阅会在所有设备上同步。这是一个无缝的世界,我不必手动同步播客订阅。同样,我也不必担心移动设备的内存会被已经在桌面上听过的剧集占用,反之亦然。播放状态会保持同步,并会自动删除已收听的剧集。如何在 web 上实现
同步应用状态数据可以交给 Background Sync API。不必立刻开始同步操作,只是时间早晚问题,甚至可能在用户已经再次关闭应用时发生。
硬件媒体键控制 #
当我在使用其他应用,比如在 Chrome 浏览器中阅读新闻页面时,仍然可以使用笔记本电脑上的媒体键控制 Podcasts 应用。无需为了跳转剧集就切换到其他应用。如何在 web 上实现
Media Session API 支持媒体键。这样用户就可以通过物理键盘、耳机上的硬件媒体键,甚至是智能手表上的软件媒体键来控制 web 应用。平滑搜寻操作的另一个方法是在用户搜索内容的重要部分时(比如通过开场字幕或章节边界)发送振动模式。
多任务处理和应用快捷方式 #
当然,我随时可以从任何地方通过多任务处理回到 Podcasts 应用。Podcasts 有一个清晰可辨的图标,可以放在桌面或应用程序坞,从而可以根据需要随时启动该应用。如何在 web 上实现
桌面和移动设备上的 PWA 可以安装到主屏幕、开始菜单或应用程序坞。安装可以基于主动提示进行,也可以完全由应用开发人员控制。《需要什么才能安装?》一文涵盖您需要了解的所有内容。在进行多任务处理时,PWA 看起来独立于浏览器。
上下文菜单中的快速操作 #
最常见的应用操作,比如搜索新内容和检查新剧集,可以直接从应用程序坞中应用的上下文菜单中调用。通过选项菜单,我还可以设置是否在登录时打开应用。
用作默认应用 #
其他 iOS 应用甚至网站或电子邮件都可以通过利用 podcasts://
URL 方案与 Podcasts 应用集成。假设我在浏览器中点击 podcasts://podcasts.apple.com/podcast/the-css-podcast/id1042283903
这样的链接,就会直接进入 Podcasts 应用来订阅或收听播客。如何在 web 上实现
现在尚无法处理完全自定义 URL 的方案,但我们目前正在为 PWA 的 URL 协议处理提案努力。目前带有 web+
的 registerProtocolHandler()
是最好的选择。
本地文件系统集成 #
您可能不会立即想到,但 Podcasts 应用自然而然地与本地文件系统集成。下载播客剧集时,它会被下载到电脑的 ~/Library/Group Containers/243LU875E5.groups.com.apple.podcasts/Library/Cache
。与 ~/Documents
不同,这个目录当然不是普通用户可以直接访问的,但它就在那里。离线内容部分引用了文件以外的其他存储机制。如何在 web 上实现
File System Access API 使开发人员能够访问设备的本地文件系统。您可以直接使用它或通过 browser-fs-access 支持库使用。这个库为不支持 API 的浏览器提供回退。出于安全原因,系统目录不可通过网络访问。
平台外观 #
对于像 Podcasts 这样的 iOS 应用,还有一个更微妙的事情是不言而喻的:文本标签都不可选的,所有文本都与机器的系统字体融合在一起。我对系统颜色主题(深色模式)的选择也得到了保留。


如何在 web 上实现
通过将user-select
CSS 属性的值设为 none
,您可以防止 UI 元素被意外选择。但是,请确保不要滥用此属性,从而导致无法选择应用内容。只应把它应用于按钮文本等 UI 元素。将 font-family
CSS 属性的值设为 system-ui
可以让您的应用使用系统默认 UI 字体。最后,您的应用可以通过尊重用户的 prefers-color-scheme
选择来遵守用户的配色方案偏好,并使用可选的深色模式切换来覆盖它。另一个需要决定的事情可能是浏览器在到达滚动区域的边界时应该采取何种操作,例如,实现自定义 pull to refresh 。这可以通过overscroll-behavior
CSS 属性实现。自定义标题栏 #
查看 Podcasts 应用窗口时,您会注意到它没有经典的集成标题栏和工具栏(例如 Safari 浏览器窗口),而是采用了一种自定义外观,看起来像停靠在主播放器窗口的侧边栏。如何在 web 上实现
虽然目前无法实现,但我们正在研究标题栏自定义。不过,您可以(并且应该)指定 display
和 theme-color
属性,以确定应用窗口的外观,并决定应该显示哪些默认浏览器控件(可能都不显示)。
生动的动画 #
Podcasts 中的动画生动流畅。例如,当我打开右侧的剧集笔记抽屉时,它会优雅地滑入。当我从下载中删除一集时,剩余的剧集会漂浮起来并占用被删除的剧集释放的屏幕空间。如何在 web 上实现
如果考虑到动画和性能一文中概述的许多最佳实践,网络上的高性能动画当然是可能的。使用 CSS Scroll Snap 功能可以大幅改进分页内容或媒体轮播中常见的滚动动画。为了实现全面控制,您可以使用 Web Animations API 。
在应用之外浮现的内容 #
iOS 上的 Podcasts 应用可以在应用之外的其他位置显示内容,例如,在系统的小组件视图中,或以 Siri 建议的形式。拥有主动的、基于使用情况的调用操作,只需点击即可与之交互,可以大大提高 Podcasts 等应用程序的再参与率。如何在 web 上实现
Content Index API 可以让应用告诉浏览器可离线使用 PWA 的哪些内容。这样一来,浏览器就可以在主应用之外显示此内容。通过将应用中有趣的内容标记为适合朗读音频播放并使用一般结构化标记,您可以让搜索引擎和虚拟助手(如 Google 智能助理)以理想的方式呈现您的产品。
锁屏媒体控制小组件 #
播放播客剧集时,Podcasts 应用会在锁屏界面显示一个漂亮的控制小组件,其中包含剧集插图、剧集标题和播客名称等元数据。如何在 web 上实现
您可以通过 Media Session API 指定插图、曲目标题等元数据,然后显示在锁屏界面、智能手表或浏览器中的其他媒体小组件上
推送通知 #
推送通知现在已经成了网上的烦恼(尽管通知提示现在安静了很多)。但如果使用得当,它们可以增加很多价值。例如,iOS Podcasts 应用可以选择性地通知我订阅的播客的新剧集或推荐新的播客,以及提醒新的应用功能。如何在 web 上实现
您可以使用 Push API 允许应用接收推送通知,从而通知用户有关 PWA 的重要事件。对于应该在未来已知时间触发且不需要网络连接的通知,您可以使用 Notification Triggers API。
应用图标标记 #
当我订阅的播客推出了新剧集时,Podcasts 主屏幕图标上的应用图标标记就会出现,再次鼓励我以不打扰的方式重新使用该应用。如何在 web 上实现
您可以使用 Badging API 设置应用图标标记。当您的 PWA 有一些“未读”项目的概念时,或者当您需要不引人注意地将用户的注意力吸引回应用时,这尤其有用。
媒体播放优先于节能设置 #
播放播客媒体时,屏幕可能会关闭,但系统不会进入待机模式。应用也可以选择让屏幕保持唤醒状态,例如显示歌词或字幕。如何在 web 上实现
Screen Wake Lock API 可防止屏幕关闭。Web 上的媒体播放会自动阻止系统进入待机模式。
通过应用商店发现应用 #
虽然 Podcasts 应用是 macOS 桌面系统内置的软件,但在 iOS 上,它需要从 App Store 安装。快速搜索 podcast
、podcasts
或 apple podcasts
可立即在 App Store 中打开该应用。如何在 web 上实现
虽然 Apple 不允许在 App Store 上使用 PWA,但在 Android 上,您可以提交使用 Trusted Web Activity 封装的 PWA。通过 bubblewrap
脚本,封装操作非常方便。此脚本也支持 PWABuilder 的 Android 应用导出功能,您无需用到命令行即可使用该功能。
功能概要 #
下表显示了所有功能的简要概述,并提供了在 Web 上实现这些功能的实用资源列表。
结论 #
自 2015 年推出以来,PWA 已经取得了长足的进步。在 Fugu 计划🐡的背景下,跨公司的 Chromium 团队正在努力缩小最后剩余的差距。通过遵循本文中的一些建议,您可以一点一点地接近那种类似应用的感觉,让用户忘记他们正在处理“只是一个网站”,因为老实说,大多数用户并不关心应用的构建方式(以及构建原因),只要它感觉像一个真正的应用就可以了。
鸣谢 #
本文由 Kayce Basques 、Joe Medley 、Joshua Bell 、Dion Almaer 、Ade Oshineye 、Pete LePage 、Sam Thorogood 、Reilly Grant 和 Jeffrey Yasskin 共同审阅。