按钮点了,页面没反应。
产品说“是不是前端没写跳转”。我一般不急着背锅,先看当前页面在哪个容器里:Safari、Chrome、微信、支付宝、App 内 WebView,结果可能完全不一样。
H5 拉起 App 这事,看着就是一行 location.href,真上线后经常变成玄学。
最常见的第一种,是 URL Scheme。
比如你们 App 约定了一个协议:
myshop://order/detail?id=98765H5 里直接跳:
functionopenByScheme(orderId) {const scheme = `myshop://order/detail?id=${encodeURIComponent(orderId)}`;window.location.href = scheme;}这东西优点很明显,简单,老系统也能接。
但我不太信这种裸跳。因为用户没装 App 的时候,浏览器不会老老实实告诉你“没装”,有的没反应,有的弹提示,有的直接报一嘴没用的错误。
所以线上一般要加一个兜底,跳失败就去下载页。
functionopenAppByScheme({ orderId, downloadUrl }) {const startAt = Date.now();const scheme = `myshop://order/detail?id=${encodeURIComponent(orderId)}`;let hidden = false;const markHidden = () => { hidden = true; };document.addEventListener('visibilitychange', () => {if (document.hidden) markHidden(); }, { once: true });window.location.href = scheme; setTimeout(() => {const cost = Date.now() - startAt;// 页面切后台了,大概率已经拉起 Appif (hidden || cost > 1800) return;window.location.href = downloadUrl; }, 1200);}这里有个细节,别用“定时器到了就跳下载页”这种写法。
有些手机拉起 App 慢一点,H5 还没来得及切后台,你下载页已经怼上去了。用户体验就是:App 被拉起来了,回来发现页面也跳没了。
第二种,是 Universal Link / App Link。
iOS 叫 Universal Link,Android 叫 App Link。它不是自定义协议,而是正常 HTTPS 链接。
比如:
https://m.example.com/app/order/98765用户装了 App,并且系统校验通过,就直接打开 App。没装 App,就当普通网页打开。
H5 代码反而更普通:
functionopenByUniversalLink(orderId) {const url = `https://m.example.com/app/order/${encodeURIComponent(orderId)}`;window.location.href = url;}这方式我更愿意推荐给新项目。
因为它不像 Scheme 那么“野”。链接本身能访问,能被分享,能被搜索引擎识别,没装 App 也能落到 H5 页面。
但它麻烦在配置,不在 JS。
iOS 要配 apple-app-site-association,Android 要配 assetlinks.json。证书、域名、路径、App 包名、签名,任何一个对不上,就只会打开网页。
前端排这种问题,别只盯代码。先拿手机直接访问这个文件,看是不是能拿到正确 JSON。再让客户端确认 App 里关联的路径有没有覆盖。
我见过不少问题,最后不是前端错,是服务端把 JSON 返回成了 text/html,或者 CDN 给加了跳转。
第三种,是 Android 的 intent 跳转。
这个主要用在 Android Chrome 里,格式长这样:
functionopenByIntent(orderId) {const fallback = encodeURIComponent('https://m.example.com/download');const intentUrl =`intent://order/detail?id=${encodeURIComponent(orderId)}` +`#Intent;scheme=myshop;package=com.example.myshop;` +`S.browser_fallback_url=${fallback};end`;window.location.href = intentUrl;}这个写法看着丑,但在 Android Chrome 里挺好用。
它能指定包名,还能带 fallback 地址。没装 App 时,浏览器知道该去哪儿,不用你自己猜。
不过这玩意别拿去 iOS 用,也别指望所有国产浏览器都完全按规矩来。有些浏览器会拦,有些 WebView 直接吞掉。
所以我线上一般会包一层判断,不让调用散落在业务代码里。
functionopenNativePage({ orderId }) {const ua = navigator.userAgent.toLowerCase();const ios = /iphone|ipad|ipod/.test(ua);const android = /android/.test(ua);const inWechat = /micromessenger/.test(ua);if (inWechat) {// 微信里很多拉起会被拦,直接给引导页更省事window.location.href = `/open-guide?orderId=${encodeURIComponent(orderId)}`;return; }if (ios) {window.location.href = `https://m.example.com/app/order/${encodeURIComponent(orderId)}`;return; }if (android) { openByIntent(orderId);return; } openAppByScheme({ orderId,downloadUrl: 'https://m.example.com/download' });}真正做 H5 跳 App,难点不在“怎么跳”,而在“失败后别傻等”。
Scheme 适合老项目,快,但兜底要自己处理。
Universal Link / App Link 更正规,适合长期维护,但配置链路长,出问题别只查前端。
Intent 适合 Android Chrome 场景,能把包名和 fallback 写清楚,但兼容性别想得太美。
我一般会把这三种都收口到一个 openNativePage 里。业务侧只传页面参数,不关心设备、不关心浏览器、不关心下载页。
否则后面改一次拉起策略,全项目搜 myshop://,那场面不会太好看。
夜雨聆风