PWA装不出去我算了五年,今天发现根子不在产品,在API——canInstall()把安装入口从浏览器手里彻底抢回来了

做了五年的 PWA,每次点「添加到主屏幕」的用户不到 3%——这个数字我盯了很久,直到把 beforeinstallprompt 的局限和 canInstall() + navigator.install() 的新方案全跑了一遍,账才算清楚。

旧方案为什么装不出去

PWA 安装的核心卡点是浏览器那个 mini-infobar——地址栏旁边冒出来的小条,用户点掉的速度比加载还快。行业平均点击率不到 3%,移动端更低,大多数用户根本不知道那个小图标是干什么的。

beforeinstallprompt 事件给了开发者一点点控制权,但限制很清楚——你只能 defer 这个提示,不能主动触发,更不能在用户真正想装的时机弹出。用户在阅读内容,你弹出安装条,体验直接打断。

canInstall() 先问再调,安装按钮不再盲猜

Chrome 143 引入的 Web Install API 带了一个关键能力:canInstall()。在显示任何安装按钮之前,先问浏览器「这个设备能装吗」,答案返回 true,按钮才出现。

const canInstall = await navigator.canInstall?.();
if (canInstall) {
  installBtn.style.display = block;
}

这步先问再调的设计,解决了过去最大的盲区——开发者不知道浏览器支不支持、用户有没有装过、是不是隐私模式,直接问系统要答案,按钮出现的那一刻就代表「能装」,不需要自己写一堆兼容性判断。

navigator.install() 把触发时机还给你

有了 canInstall() 打前站,install() 的用法极度简单:

installBtn.addEventListener(click, async () => {
  try {
    const result = await navigator.install();
    console.log(安装成功, result.manifestId);
  } catch (e) {
    if (e.name === AbortError) {
      // 用户取消,什么都不做
    } else if (e.name === DataError) {
      // manifest 无效,检查 manifest.json
    }
    // 隐私模式下 promise 直接 reject
  }
});

Promise 的错误设计覆盖了所有状态——安装成功、用户拒绝、manifest 找不到、隐私模式——不需要记乱七八糟的事件名,也不需要自己管理 deferredPrompt 变量。

完整的安装体验闭环

把这三件事串起来,就是一个完整的安装体验:

class PWAInstallManager {
  constructor() {
    this.installBtn = document.getElementById(install-btn);
  }

  async init() {
    // 第一步:问能不能装,不行就隐藏按钮
    if (!(await navigator.canInstall?.())) {
      this.installBtn.style.display = none;
      return;
    }
    this.installBtn.style.display = block;
    this.installBtn.addEventListener(click, () => this.install());
  }

  async install() {
    try {
      const { manifestId } = await navigator.install();
      this.showSuccess();
    } catch (e) {
      if (e.name === AbortError) return;
      this.showError(e.message);
    }
  }
}

这个模式的核心变化是:安装按钮的出现时机完全由你控制——用户在阅读页面内容时,按钮可以藏着;当他完成注册、收藏某个内容、或停留超过一定时间,再显示安装入口,转化率完全是另一回事。

配合 Web Share API 的 canShare() 用,效果叠加

Web Share API Level 2 的 canShare() 用了同样的设计逻辑——分享前先问系统能不能处理:

if (navigator.canShare?.({ files: [fileBlob] })) {
  shareBtn.classList.add(active);
}

两个 API 用法一致,先问再调,不行就灰掉按钮而不是点了没反应。用户感知到的体验是:出现的就是能用的,不能用的根本不会出来。

浏览器支持现状

navigator.canInstall() 和 navigator.install() 目前在 Chrome/Edge 143+ 默认开启,139-142 版本可通过 about:flags 手动开启。Firefox 和 Safari 目前会忽略这些调用,继续走各自的「添加到主屏幕」路径——所以 canInstall 的判断是必须的,没有这个前置检查,安装按钮点了就是无效操作。

怎么落地

三步走:

第一步,用 canInstall() 替代原来的 beforeinstallprompt 监听,作为显示安装按钮的前置条件。

第二步,在用户完成关键行为后(注册、收藏、停留时长超过阈值)才显示安装按钮,不要首屏就弹。

第三步,安装成功后给出明确的成功反馈,告诉用户「已安装到主屏幕」,而不是只返回一个 manifestId 就完了。

这件事的本质是把安装入口从「地址栏旁边的那个小图标」变成「你页面里一个随时可控的按钮」。用户体验变了,转化率才有可能变。

评论区

0 条评论

登录后可评论。

阿跨·跨端开发 923 阅读