让您的 PWA 感觉更像应用

让您的渐进式 Web 应用感觉不像网站,而像“真正的”应用

当您玩渐进式 Web 应用流行语宾果游戏时,“PWA 只是网站”是一个稳妥的选择。 Microsoft 的 PWA 文档也认同,我们在这个网站上也这样说,甚至 PWA 提名者 Frances Berriman 和 Alex Russell 也这样写道。 是的,PWA 只是网站,但它们也远不止于此。 如果做得好,PWA 不会感觉像网站,而像“真正的”应用。 那么,感觉像真正的应用是什么意思呢?

为了回答这个问题,让我以 Apple Podcasts 应用为例。 它在桌面上的 macOS 和移动设备上的 iOS(以及 iPadOS)上均可用。 虽然 Podcasts 是一个媒体应用程序,但我借助它来说明的核心思想也适用于其他类别的应用。

An iPhone and a MacBook side by side, both running the Podcasts application.
iPhone 和 macOS 上的 Apple Podcasts(来源)。

能够离线运行

如果您退后一步,想想您的手机或台式计算机上的特定于平台的应用程序,那么有一件事非常突出:您永远不会一无所获。 在 Podcasts 应用中,即使我处于离线状态,也总是有内容。 当没有网络连接时,该应用自然仍然会打开。 热门排行榜 部分不显示任何内容,而是回退到 目前无法连接 消息,并配有一个 重试 按钮。 这可能不是最受欢迎的体验,但我得到了一些东西。

The Podcasts app showing a 'Cannot connect right now.' info message when no network connection is available.
没有网络连接的 Podcasts 应用。
如何在 Web 上实现此功能

Podcasts 应用遵循所谓的应用 Shell 模型。 显示核心应用所需的所有静态内容都缓存在本地,包括装饰性图像(如左侧菜单图标和核心播放器 UI 图标)。 动态内容(如 热门排行榜 数据)仅按需加载,如果加载失败,则可以使用本地缓存的回退内容。 阅读文章 应用 Shell 模型,了解如何将此架构模型应用于您的 Web 应用。

离线内容可用且媒体可播放

在离线状态下,通过左侧抽屉,我仍然可以导航到 已下载 部分,并欣赏已下载的播客剧集,这些剧集已准备好播放,并显示所有元数据,如作品和描述。

Podcasts app with a downloaded episode of a podcast playing.
即使没有网络,也可以播放已下载的播客剧集。
如何在 Web 上实现此功能

先前下载的媒体内容可以从缓存中提供,例如使用 Workbox 库的提供缓存的音频和视频 配方。 其他内容始终可以存储在缓存中,或存储在 IndexedDB 中。 阅读文章 Web 存储 以获取所有详细信息,并了解何时使用哪种存储技术。 如果您有应持久存储的数据,而没有在可用内存不足时被清除的风险,则可以使用 持久存储 API

主动后台下载

当我重新联网时,我当然可以使用类似 http 203 的查询搜索内容,当我决定订阅搜索结果 HTTP 203 播客时,该系列的最新剧集会立即下载,无需任何询问。

The Podcasts app downloading the latest episode of a podcast immediately after subscribing.
订阅播客后,最新剧集会立即下载。
如何在 Web 上实现此功能

下载播客剧集可能需要更长的时间。 Background Fetch API 让您可以将下载委托给浏览器,浏览器会在后台处理这些下载。 在 Android 上,浏览器甚至可以将这些下载进一步委托给操作系统,因此浏览器不需要持续运行。 下载完成后,您应用的服务工作线程将被唤醒,您可以决定如何处理响应。

共享到其他应用程序并与之交互

Podcasts 应用自然地与其他应用程序集成。 例如,当我右键单击我喜欢的剧集时,我可以将其共享到我设备上的其他应用,例如“消息”应用。 它还自然地与系统剪贴板集成。 我可以右键单击任何剧集并复制其链接。

The Podcasts app's context menu invoked on a podcast episode with the 'Share Episode → Messages' option selected.
将播客剧集共享到“消息”应用。
如何在 Web 上实现此功能

Web Share APIWeb Share Target API 允许您的应用与其他应用程序共享和接收文本、文件和链接。 尽管 Web 应用尚无法将菜单项添加到操作系统内置的右键单击菜单中,但有很多其他方法可以链接到设备上的其他应用和从其他应用链接。 借助 Async Clipboard API,您可以以编程方式将文本和图像数据(PNG 图像)读取和写入到系统剪贴板。 在 Android 上,您可以使用 Contact Picker API 从设备的联系人管理器中选择条目。 如果您同时提供特定于平台的应用和 PWA,则可以使用 Get Installed Related Apps API 来检查是否已安装特定于平台的应用,在这种情况下,您无需鼓励用户安装 PWA 或接受 Web 推送通知。

后台应用刷新

在 Podcasts 应用的设置中,我可以配置应用自动下载新剧集。 这样一来,我甚至不必考虑它,更新的内容将始终在那里。 太神奇了。

The Podcasts app's settings menu in the 'General' section where the 'Refresh Podcasts' option is set to 'Every Hour'.
Podcasts 配置为每小时检查一次新的播客剧集。
如何在 Web 上实现此功能

Periodic Background Sync API 允许您的应用在后台定期刷新其内容,而无需运行。 这意味着新内容会主动可用,因此您的用户可以在他们决定时立即开始深入了解。

状态在云端同步

同时,我的订阅会在我拥有的所有设备上同步。 在一个无缝的世界中,我不必担心手动保持我的播客订阅同步。 同样,我不必担心我的移动设备的内存会被我在桌面上已经听过的剧集消耗掉。 播放状态保持同步,已收听的剧集会自动删除。

The Podcasts app's settings menu in the 'Advanced' section where the 'Sync subscriptions across devices' option is activated.
状态在云端同步。
如何在 Web 上实现此功能

同步应用状态数据是一项您可以委托给 Background Sync API 的任务。 同步操作本身不必立即发生,只需最终发生,甚至可能在用户再次关闭应用后发生。

硬件媒体键控件

当我忙于另一个应用程序时,例如在 Chrome 浏览器中阅读新闻页面,我仍然可以使用笔记本电脑上的媒体键控制 Podcasts 应用。 无需切换到该应用即可向前或向后跳过。

Apple MacBook Pro Magic Keyboard with annotated media keys.
媒体键允许控制 Podcasts 应用(来源)。
如何在 Web 上实现此功能

Media Session API 支持媒体键。 这样,用户就可以使用物理键盘、耳机上的硬件媒体键,甚至可以从智能手表上的软件媒体键控制 Web 应用。 使搜索操作更流畅的另一个想法是在用户搜索内容的重要部分(例如,跳过片头或章节边界)时发送 振动模式

多任务处理和应用快捷方式

当然,我可以随时从任何位置多任务处理返回到 Podcasts 应用。 该应用有一个清晰可辨的图标,我也可以将其放在我的桌面或应用程序坞上,以便在我想要时立即启动 Podcasts。

The macOS task switcher with a number of app icons to choose from, one of them the Podcasts app.
多任务处理返回到 Podcasts 应用。
如何在 Web 上实现此功能

桌面和移动设备上的渐进式 Web 应用都可以安装到主屏幕、开始菜单或应用程序坞。 安装可以基于主动提示发生,也可以完全由应用开发者控制。 文章 成为可安装的需要什么? 涵盖了您需要了解的一切。 进行多任务处理时,PWA 看起来独立于浏览器。

上下文菜单中的快速操作

最常见的应用操作,搜索新内容和 检查新剧集,可直接从 Dock 中应用的上下文菜单中使用。 通过 选项 菜单,我还可以决定在登录时打开应用。

Podcasts app icon context menu showing the 'Search' and 'Check for New Episodes' options.
快速操作可直接从应用图标立即使用。
如何在 Web 上实现此功能

通过在 PWA 的 Web 应用清单中指定 应用图标快捷方式,您可以注册用户可以直接从应用图标访问的常用任务的快速路由。 在 macOS 等操作系统上,用户还可以右键单击应用图标并将应用设置为在登录时启动。 目前正在进行关于 登录时运行 的提案。

充当默认应用

其他 iOS 应用程序,甚至网站或电子邮件,都可以通过利用 podcasts:// URL 方案与 Podcasts 应用集成。 如果我在浏览器中点击类似 podcasts://podcasts.apple.com/podcast/the-css-podcast/id1042283903 的链接,我会被直接带入 Podcasts 应用,并且可以决定订阅或收听播客。

The Chrome browser showing a confirmation dialog asking the user whether they want to open the Podcasts app.
可以直接从浏览器打开 Podcasts 应用。
如何在 Web 上实现此功能

尚无法处理完全自定义的 URL 方案,但目前正在进行关于 PWA 的 URL 协议处理 的提案。 目前,带有 web+ 方案前缀的 registerProtocolHandler() 是最佳替代方案。

本地文件系统集成

您可能不会立即想到它,但 Podcasts 应用自然地与本地文件系统集成。 当我下载播客剧集时,在我的笔记本电脑上,它存储在 ~/Library/Group Containers/243LU875E5.groups.com.apple.podcasts/Library/Cache 中。 与 ~/Documents 不同,当然,此目录并非旨在供普通用户直接访问,但它确实存在。 离线内容 部分引用了文件以外的其他存储机制。

The macOS Finder navigated to the Podcasts app's system directory.
播客剧集存储在特殊的系统应用文件夹中。
如何在 Web 上实现此功能

File System Access API 使开发者能够访问设备的本地文件系统。 您可以直接使用它,也可以通过 browser-fs-access 支持库使用它,该库透明地为不支持 API 的浏览器提供回退。 出于安全原因,系统目录是 Web 不可访问的。

平台外观和风格

对于像 Podcasts 这样的 iOS 应用程序来说,有一件更微妙的事情是不言而喻的:没有文本标签是可选择的,并且所有文本都与机器的系统字体融为一体。 而且,我还选择了系统颜色主题(深色模式)也得到了尊重。

The Podcasts app in dark mode.
Podcasts 应用支持浅色和深色模式。
The Podcasts app in light mode.
该应用使用默认系统字体。
如何在 Web 上实现此功能

通过使用 user-select CSS 属性,并将值设置为 none,您可以保护 UI 元素免于被意外选中。 但是,请务必不要滥用此属性来使应用内容不可选择。 它应该仅用于按钮文本等 UI 元素。 system-ui font-family CSS 属性的值允许您指定用于应用的系统默认 UI 字体。 最后,您的应用可以通过尊重用户的 prefers-color-scheme 选择来遵守用户的配色方案偏好,并使用可选的 深色模式切换 来覆盖它。 另一个需要决定的事情可能是当到达滚动区域的边界时浏览器应该做什么,例如,实现自定义的下拉刷新。 这可以使用 overscroll-behavior CSS 属性来实现。

自定义标题栏

当您查看 Podcasts 应用窗口时,您会注意到它没有经典的集成标题栏和工具栏,例如 Safari 浏览器窗口,而是自定义的体验,看起来像停靠在主播放器窗口上的侧边栏。

The Safari browser's integrated tile bar and toolbar.
The Podcasts app's customized split customized title bar.
Safari 和 Podcasts 的自定义标题栏。
如何在 Web 上实现此功能

虽然目前尚不可能,但 标题栏自定义 目前正在开发中。 但是,您可以(并且应该)指定 Web 应用清单的 displaytheme-color 属性,以确定应用程序窗口的外观和风格,并决定应显示哪些默认浏览器控件(可能不显示任何控件)。

流畅的动画

Podcasts 中的应用内动画流畅而平滑。 例如,当我打开右侧的 剧集注释 抽屉时,它会优雅地滑入。 当我从下载中删除一个剧集时,剩余的剧集会向上浮动并占用已删除剧集释放的屏幕空间。

The Podcasts app with the 'Episode Notes' drawer expanded.
打开抽屉等应用内动画非常流畅。
如何在 Web 上实现此功能

如果您考虑到文章 动画和性能 中概述的许多最佳实践,那么在 Web 上实现高性能动画当然是可能的。 通过使用 CSS Scroll Snap 功能,可以极大地改进分页内容或媒体轮播中常见的滚动动画。 为了实现完全控制,您可以使用 Web Animations API

应用外部显示的内容

iOS 上的 Podcasts 应用可以在实际应用程序以外的其他位置显示内容,例如,在系统的“小组件”视图中,或以 Siri 建议的形式显示。 具有主动的、基于使用情况的行动号召(只需点击一下即可与之交互)可以大大提高像 Podcasts 这样的应用的重新参与率。

iOS Widget view showing the Podcasts app suggesting a new episode of a podcast.
应用内容在主 Podcasts 应用外部显示。
如何在 Web 上实现此功能

Content Index API 允许您的应用程序告诉浏览器 PWA 的哪些内容可离线使用。 这允许浏览器在主应用外部显示此内容。 通过在您的应用中将有趣的内容标记为适合 可语音 音频播放,并通过普遍使用 结构化标记,您可以帮助搜索引擎和虚拟助手(如 Google Assistant)以理想的方式呈现您的产品。

锁屏媒体控制小组件

当播客剧集正在播放时,Podcasts 应用会在锁屏上显示一个美观的控制小组件,其中包含剧集作品、剧集标题和播客名称等元数据。

iOS media playback widget on the lock screen showing a podcast episode with rich metadata.
在应用中播放的媒体可以从锁屏控制。
如何在 Web 上实现此功能

Media Session API 允许您指定元数据,如作品、曲目标题等,然后这些元数据会显示在锁屏、智能手表或浏览器中的其他媒体小组件上。

推送通知

推送通知在 Web 上已变得有点烦人(尽管 通知提示现在安静了很多)。 但如果使用得当,它们可以增加很多价值。 例如,iOS Podcasts 应用可以选择性地通知我我订阅的播客的新剧集或推荐新剧集,以及提醒我注意新的应用功能。

iOS Podcasts app in the 'Notifications' settings screen showing the 'New Episodes' notifications toggle activated.
应用可以发送推送通知,以告知用户有关新内容的信息。
如何在 Web 上实现此功能

Push API 允许您的应用接收推送通知,以便您可以通知您的用户有关 PWA 周围的值得注意的事件。 对于应在未来已知时间触发且不需要网络连接的通知,您可以使用 Notification Triggers API

应用图标徽章

每当我的某个订阅播客有新剧集可用时,Podcasts 主屏幕图标上就会显示一个应用图标徽章,再次鼓励我以一种不突兀的方式重新参与该应用。

iOS settings screen showing the 'Badges' toggle activated.
徽章是应用程序告知用户有关新内容的微妙方式。
如何在 Web 上实现此功能

您可以使用 Badging API 设置应用图标徽章。 当您的 PWA 具有某些“未读”项目的概念,或者当您需要一种不引人注目地将用户的注意力吸引回应用的方式时,这尤其有用。

媒体播放优先于节能器设置

当播客媒体正在播放时,屏幕可能会关闭,但系统不会进入待机模式。 应用可以选择性地使屏幕保持唤醒状态,例如,显示歌词或字幕。

macOS Preferences in the 'Energy Saver' section.
应用可以使屏幕保持唤醒状态。
如何在 Web 上实现此功能

Screen Wake Lock API 允许您防止屏幕关闭。 Web 上的媒体播放会自动阻止系统进入待机模式。

通过应用商店发现应用

虽然 Podcasts 应用是 macOS 桌面体验的一部分,但在 iOS 上需要从 App Store 安装。 快速搜索 podcastpodcastsapple podcasts 会立即在 App Store 中找到该应用。

iOS App Store search for 'podcasts' reveals the Podcasts app.
用户已经学会了在应用商店中发现应用。
如何在 Web 上实现此功能

虽然 Apple 不允许 PWA 上架 App Store,但在 Android 上,您可以提交您的 PWA 包装在 Trusted Web Activity 中bubblewrap 脚本使这成为一个轻松的操作。 此脚本也是 PWABuilder 的 Android 应用导出功能在内部支持的功能,您无需接触命令行即可使用该功能。

功能摘要

下表显示了所有功能的简要概述,并提供了在 Web 上实现这些功能的有用资源列表。

结论

自 2015 年推出以来,PWA 已经取得了长足的进步。 在 Project Fugu 🐡 的背景下,跨公司的 Chromium 团队正在努力弥合最后剩余的差距。 通过遵循本文中的一些建议,您可以逐步接近那种类似应用的感觉,并让您的用户忘记他们正在处理“只是一个网站”,因为,老实说,他们中的大多数人并不关心您的应用是如何构建的(他们为什么要关心呢?),只要它感觉像一个真正的应用。

致谢

本文由 Kayce BasquesJoe MedleyJoshua BellDion AlmaerAde OshineyePete LePageSam ThorogoodReilly GrantJeffrey Yasskin 审阅。