确定用户是否从移动Safari导航。

85

我有一个应用程序,希望根据用户从哪里导航来重定向到不同的页面。

如果从 Web Clip 导航,请不要重定向。 如果从移动 Safari 导航,请重定向到 safari.aspx。 如果从其他任何地方导航,请重定向到 unavailable.aspx

我能够使用 iPhone WebApps,有没有办法检测它是如何加载的?主屏幕还是 Safari? 来确定用户是否从 Web Clip 导航,但我无法确定用户是否从 iPhone 或 iPod 上的移动 Safari 导航。

这是我的代码:

if (window.navigator.standalone) {
    // user navigated from web clip, don't redirect
}
else if (/*logic for mobile Safari*/) {
    //user navigated from mobile Safari, redirect to safari page
    window.location = "safari.aspx";
}
else {
    //user navigated from some other browser, redirect to unavailable page
    window.location = "unavailable.aspx";
}

https://dev59.com/EGox5IYBdhLWcg3wr2To - Ali
@Ali,那个问题并不是在询问与此相同的东西。 - Jake Millington
15个回答

185

请见https://developer.chrome.com/multidevice/user-agent#chrome_for_ios_user_agent - Safari和Chrome在iOS上的用户代理字符串非常相似:

Chrome

Mozilla/5.0(iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en)AppleWebKit/534.46.0(KHTML, like Gecko)CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3

Safari

Mozilla/5.0(iPhone; U; CPU like Mac OS X; en)AppleWebKit/420+(KHTML, like Gecko)Version/3.0 Mobile/1A543 Safari/419.3

看起来这里的最佳方法是首先检查iOS,如其他答案所建议的那样,然后过滤使Safari UA独特的内容,我建议最好使用“is AppleWebKit and is not CriOS”来实现:

var ua = window.navigator.userAgent;
var iOS = !!ua.match(/iPad/i) || !!ua.match(/iPhone/i);
var webkit = !!ua.match(/WebKit/i);
var iOSSafari = iOS && webkit && !ua.match(/CriOS/i);

1
@fabricioflores 这就是生活 :( - unwitting
9
这里唯一一个覆盖了所有规格的答案。我只会将iOS测试正则表达式更改为/iP(ad|hone)/i,这样就不需要使用或运算符。 - sareed
3
@sareed 我总是觉得能够使用正则表达式匹配 iP(ad|od|hone) 非常令人满意。 - unwitting
9
愚蠢的iOS Opera仍会在上面的代码中传递"true",因此我将最后一行更改为: var iOSSafari = iOS && webkit && !ua.match(/CriOS/i) && !ua.match(/OPiOS/i); - Hooman Askari
7
好的回答。但是为什么在match中要使用双重否定 (!!),而不是使用test()呢? - OMA
显示剩余9条评论

36

更新:这是一个非常老的答案,我不能删除它,因为该答案已被接受。请参考下面 unwitting的答案 ,这是一个更好的解决方案。


你可以在用户代理字符串中检查“iPad”或“iPhone”子字符串:

var userAgent = window.navigator.userAgent;

if (userAgent.match(/iPad/i) || userAgent.match(/iPhone/i)) {
   // iPad or iPhone
}
else {
   // Anything else
}

11
我认为不是这样的。相对于Safari iOS浏览器来说,其他的iPhone/iPad浏览器都适用。我认为这个表达方式不仅排除了Safari,还排除了所有的iPhone/iPad浏览器。 - Kamilius
19
这不是正确答案,因为如果我在iOS Chrome中访问它,它会返回true。 - Bobby Stenly
7
这行代码无法正常工作,因为用户可能正在使用 iOS 上的移动版 Chrome 进行浏览,但是此时它仍会返回 true,即使不是移动版 Safari。 - jangosteve
2
这并不检测用户是否使用移动版Chrome。而且相比Safari,Chrome iOS拥有非常古老的JavaScript引擎(由于苹果的政策),导致某些渲染无法实现。 - Aero Wang
3
这不应被标记为正确答案,因为它包括在iPhone上运行的任何浏览器。问题明确要求检测移动版Safari浏览器。 - Shashank
显示剩余2条评论

27

最佳实践是:

function isMobileSafari() {
    return navigator.userAgent.match(/(iPod|iPhone|iPad)/) && navigator.userAgent.match(/AppleWebKit/)
}

28
这也适用于Chrome iOS :( https://dev59.com/CmYr5IYBdhLWcg3wRoSW#13808053 - httpete
这也可以检测移动版的Chrome。 - sean
我们应该如何区分iOS Safari和iOS Chrome呢?有什么想法吗? - Burak Karakuş
这两个正则表达式可以很容易地合并。 - Antoine
2
这也适用于桌面版Safari。 - Antoine

12

合并了所有回答和评论。这是结果。

function iOSSafari(userAgent) {
    return /iP(ad|od|hone)/i.test(userAgent) &&
      /WebKit/i.test(userAgent) &&
      !(/(CriOS|FxiOS|OPiOS|mercury)/i.test(userAgent));
}


// Usage:
// if iOSSafari(window.navigator.userAgent) { /* iOS Safari code here */ }

// Testing:
var iPhoneSafari = [
  "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1",
  "Mozilla/5.0 (Apple-iPhone7C2/1202.466; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543 Safari/419.3",
  "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1",
  "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1",
  "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A5370a Safari/604.1",
  "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1",
  "Mozilla/5.0 (iPhone9,3; U; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1",
  "Mozilla/5.0 (iPhone9,4; U; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1",
];
console.log("All true:", iPhoneSafari.map(iOSSafari));

var iPhoneNotSafari = [
  "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/69.0.3497.105 Mobile/15E148 Safari/605.1",
  "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/13.2b11866 Mobile/16A366 Safari/605.1.15",
];
console.log("All false:", iPhoneNotSafari.map(iOSSafari));

我猜这也适用于桌面版Safari(WebKit)。 - Antoine
1
@antoine129 不,它不会(在OS X El Capitan上测试过)- 正则表达式是ANDed,我猜桌面版Safari从不发送iP* ... - Christian Ulbrich

9
下落的代码只在移动Safari中找到,其他浏览器(除了海豚和其他小型浏览器)都没有。
  (/(iPad|iPhone|iPod)/gi).test(userAgent) &&
  !(/CriOS/).test(userAgent) &&
  !(/FxiOS/).test(userAgent) &&
  !(/OPiOS/).test(userAgent) &&
  !(/mercury/).test(userAgent)

1
这是唯一区分 Firefox 和 Opera 在 iOS 上的方法。 - nord-stream

6
看到所有的答案,以下是关于所提议的 RegExes 的一些提示:
  • AppleWebKit 匹配桌面版 Safari(不仅限于移动版)
  • 对于如此简单的正则表达式,无需多次调用 .match,而应优先使用更轻量级的 .test 方法。
  • 全局(global)正则表达式标志 g 是无用的,而忽略大小写的 i 可以有用
  • 不需要捕获(括号),我们只是在测试字符串

我仅使用此方法,因为对于移动版 Chrome 获得 true 对我来说是可以的(行为相同):

/iPhone|iPad|iPod/i.test(navigator.userAgent)

我只想检测设备是否是iOS应用程序的目标。


2
关于正则表达式的重要注意事项(总的来说),感谢您在这里指出!除了您的答案之外,现在人们还应该检查!window.MSStream来排除微软浏览器,如另一个问题中所述。而对于那些想过滤 Chrome 的人来说,!/CriOS/.test(navigator.userAgent)就是根据这个答案的。 - caw

3

这个正则表达式适用于我,简洁明了:

const isIOSSafari = !!window.navigator.userAgent.match(/Version\/[\d\.]+.*Safari/);

如果宿主应用程序没有明确将“Safari”添加到自定义用户代理字符串中,那么这将无法与嵌入式WKWebView一起使用。 - mrgrieves
1
iPad Safari现在默认处于横屏状态。 - Csaba Toth

1

我知道这是一个旧的帖子,但我想与大家分享我的解决方案。

我需要检测用户何时从桌面版Safari导航(因为我们在2017年中期,而Apple还没有支持input[type="date"])。

所以,我为其制作了一个备用自定义日期选择器。但仅适用于桌面版Safari,因为该输入类型在移动版Safari中运行良好。因此,我制作了这个正则表达式来仅检测桌面版Safari。我已经测试过它,并且不会与Opera、Chrome、Firefox或Safari Mobile匹配。

希望它能对你们中的一些人有所帮助。

if(userAgent.match(/^(?!.*chrome).(?!.*mobile).(?!.*firefox).(?!.*iPad).(?!.*iPhone).*safari.*$/i)){
  $('input[type="date"]').each(function(){
    $(this).BitmallDatePicker();
  })
}

1

实际上,没有一种完美的方法可以检测移动版Safari浏览器。有很多浏览器可能会使用移动版Safari的用户代理关键字。也许您可以尝试特征检测并不断更新规则。


1
不是在我的情况下。例如,我想展示为iOS+Chrome定制的智能横幅,但不想在iOS+Safari上展示此横幅,因为iOS+Safari已经有内置的智能横幅了。 - JobaDiniz
@JobaDiniz 我知道很多人想在移动Safari中做一些事情,而不是在其他浏览器中,我也是。然而,事实上,匹配像“iPad”、“iOS”、“iPhone”等标记的浏览器并不意味着它们是移动Safari,也许它们是iOS的其他浏览器。例如,中国的UC和QQ浏览器。所有这些浏览器都可以使用Safari的核心,并且大多数行为与移动Safari相同,但有些不同。这很糟糕。 - Light
确实很糟糕。我太傻了,曾经希望navigator.appname会与众不同,所有的iOS浏览器都是“Netscape”。然而,Safari是唯一能够安装配置文件的浏览器,所以如果其他任何浏览器获取了一个.mobileconfig文件,就会下载它并且显得很愚蠢,因为它不知道哪个应用程序可以打开它... - Mitch Match

1

我点赞了@unwitting的回答,因为它让我不可避免地开始了。然而,在iOS Webview中呈现我的SPA时,我需要稍微调整一下。

function is_iOS () {
    /*
        Returns (true/false) whether device agent is iOS Safari
    */
    var ua = navigator.userAgent;
    var iOS = !!ua.match(/iPad/i) || !!ua.match(/iPhone/i);
    var webkitUa = !!ua.match(/WebKit/i);

    return typeof webkitUa !== 'undefined' && iOS && webkitUa && !ua.match(/CriOS/i);
};

主要区别在于将webkit重命名为webkitUa,以防止与根webkit对象发生冲突,该对象用作SPA和UIView之间的消息处理程序。

这个可行。返回 typeof webkitUa !== 'undefined' && iOS && webkitUa && !ua.match(/CriOS/i); - Vignesh Chinnaiyan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接