关注 Uzero 公众号, 更多前端小干货等着你喔!
前言
看到标题, 大家就能想起这个需求在很多项目上都能用到. 我们部署在 web 服务器上的前端应用, 既可以用 PC 浏览器访问, 也可以用手机浏览器访问, 再加上现在智能设备的推广, 我们甚至能在车载系统, 穿戴设备和电视平台上访问.
设备的多样化让用户无处不在, 有时候我们需要根据不同的浏览器运行环境做出对应的处理. 浏览器是 JavaScript 的承载体, 我们可以从浏览器上获取相关的信息, 来进一步处理我们的业务逻辑.
然而浏览器品牌众多, 有些浏览器使用的标准也不太一样, 造就了难以统一的判断. 下面我大概罗列一下常用的浏览器品牌和在什么情况下使用浏览器运行环境判断. 浏览器相关统计数据可以参考这里 https://gs.statcounter.com/browser-market-share .
国际五大浏览器品牌: 按照全球使用率降序排列
- Google Chrome:Windows,MacOS,Linux,Android,iOS
- Apple Safari:MacOS,iOS
- Mozilla Firefox:Windows,MacOS,Linux,Android,iOS
- ASA Opera:Windows,MacOS,Linux,Android,iOS
Microsoft Internet Explorer 或 Microsoft Edge:Windows
国产常用浏览器品牌: 按照国内使用率降序排列, 普遍基于开源项目 Chromium 进行开发
微信浏览器
QQ 浏览器
UC 浏览器
2345 浏览器
搜狗浏览器
猎豹浏览器
遨游浏览器
百度浏览器: 百度在 2019 年 04 月 30 日宣布停止服务
其他浏览器: 很多很多, 数不清, 我就不列出来了
顺便吐槽一下这个不要脸的红芯浏览器, 明明就是基于 Chromium 进行二次开发再套多一层外壳, 还非得说自己开发的浏览器是世界第五大浏览器, 偷吃不抹嘴, 还是被眼尖的网友发现了. 详情请戳 one,two https://www.sohu.com/a/248218289_379822 ,three https://www.sohu.com/a/247793569_116132 ....
使用场景
判断用户浏览器是桌面端还是移动端, 显示对应的主题样式
判断用户浏览器是 Android 端还是 iOS 端, 跳转到对应的 App 下载链接
判断用户浏览器是微信端还是 H5 端, 调用微信分享或当前浏览器分享
获取用户浏览器的内核和载体, 用于统计用户设备平台分布区间
获取用户浏览器的载体版本, 用于提示更新信息
其实还有很多使用场景, 就不一一举例了
原理
针对处理一个这样的使用场景, 其实有一个比较专业的名字, 叫做浏览器指纹. 我们上面谈到的需求也只是浏览器指纹方案里面的一小部分, 而我们需要使用到的浏览器指纹就是 UserAgent.
这个 UserAgent 是何方神圣呢, 中文翻译过来就是用户代理. 引用百度的定义, 就是一个特殊字符串头, 使得服务器能够识别客户使用的操作系统及版本, CPU 类型, 浏览器载体及版本, 浏览器渲染引擎, 浏览器语言, 浏览器插件等. 而这些信息也足够我们去判断浏览器运行环境了.
准备
目前网上很多解决方法都只是针对系统是否是桌面端还是移动端, Android 端还是 iOS 端, 部分浏览器载体的判断和获取等等, 没有一个比较完美或者终极的解决方案.
因此我用了很多测试平台整理出一个比较全面的解决方案. 这个方案包含浏览器系统及版本, 浏览器平台, 浏览器内核及版本, 浏览器载体及版本, 浏览器外壳及版本.
而此方案也是基于 navigator.userAgent 获取相关浏览器信息(如下), 再通过系统, 平台, 内核, 载体, 外壳的特有字段进行归类统一, 整理出一个完整的浏览器运行环境.
- const ua = navigator.userAgent.toLowerCase();
- // 输出
- "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"
浏览器信息: 权重按照以下降序排列
浏览器系统: 所运行的操作系统, 包含 Windows,MacOS,Linux,Android,iOS
浏览器平台: 所运行的设备平台, 包含 Desktop 桌面端, Mobile 移动端
浏览器内核: 浏览器渲染引擎, 包含 Webkit,Gecko,Presto,Trident
浏览器载体: 五大浏览器品牌, 包含 Chrome,Safari,Firefox,Opera,Iexplore/Edge
浏览器外壳: 基于五大浏览器品牌的内核进行开发, 再套一层自研技术的外壳, 如国内众多浏览器品牌
获取 UserAgent 是否包含字段: 判断是否包含系统, 平台, 内核, 载体, 外壳的特有字段
const testUa = regexp => regexp.test(ua);
获取 UserAgent 对应字段的版本
const testVs = regexp => (ua.match(regexp) + "").replace(/[^0-9|_.]/ig,"").replace(/_/ig, ".");
方案
上述准备工作完成后, 我们就按照权重 (<font color="#f66"> 系统 + 系统版本> 平台> 内核 + 载体 + 内核版本 + 载体版本> 外壳 + 外壳版本 </font>) 根据系统, 平台, 内核, 载体, 外壳的特有字段来归类统一浏览器运行环境.
系统 + 系统版本
- // 系统
- let system = "unknown";
- if (testUa(/Windows|win32|win64|wow32|wow64/ig)) {
- system = "windows"; // Windows 系统
- } else if (testUa(/macintosh|macintel/ig)) {
- system = "macos"; // macos 系统
- } else if (testUa(/x11/ig)) {
- system = "linux"; // Linux 系统
- } else if (testUa(/Android|adr/ig)) {
- system = "android"; // Android 系统
- } else if (testUa(/iOS|iPhone|iPad|ipod|iwatch/ig)) {
- system = "ios"; // iOS 系统
- }
- // 系统版本
- let systemVs = "unknown";
- if (system === "windows") {
- if (testUa(/Windows nt 5.0|Windows 2000/ig)) {
- systemVs = "2000";
- } else if (testUa(/Windows nt 5.1|Windows xp/ig)) {
- systemVs = "xp";
- } else if (testUa(/Windows nt 5.2|Windows 2003/ig)) {
- systemVs = "2003";
- } else if (testUa(/Windows nt 6.0|Windows vista/ig)) {
- systemVs = "vista";
- } else if (testUa(/Windows nt 6.1|Windows 7/ig)) {
- systemVs = "7";
- } else if (testUa(/Windows nt 6.2|Windows 8/ig)) {
- systemVs = "8";
- } else if (testUa(/Windows nt 6.3|Windows 8.1/ig)) {
- systemVs = "8.1";
- } else if (testUa(/Windows nt 10.0|Windows 10/ig)) {
- systemVs = "10";
- }
- } else if (system === "macos") {
- systemVs = testVs(/os x [\d._]+/ig);
- } else if (system === "android") {
- systemVs = testVs(/Android [\d._]+/ig);
- } else if (system === "ios") {
- systemVs = testVs(/os [\d._]+/ig);
- }
平台
- let platform = "unknow";
- if (system === "windows" || system === "macos" || system === "linux") {
- platform = "desktop"; // 桌面端
- } else if (system === "android" || system === "ios" || testUa(/mobile/ig)) {
- platform = "mobile"; // 移动端
- }
内核 + 载体
- let engine = "unknow";
- let supporter = "unknow";
- if (testUa(/applewebkit/ig) && testUa(/Safari/ig)) {
- engine = "webkit"; // webkit 内核
- if (testUa(/edge/ig)) {
- supporter = "edge"; // edge 浏览器
- } else if (testUa(/opr/ig)) {
- supporter = "opera"; // opera 浏览器
- } else if (testUa(/Chrome/ig)) {
- supporter = "chrome"; // Chrome 浏览器
- } else {
- supporter = "safari"; // Safari 浏览器
- }
- } else if (testUa(/gecko/ig) && testUa(/Firefox/ig)) {
- engine = "gecko"; // gecko 内核
- supporter = "firefox"; // Firefox 浏览器
- } else if (testUa(/presto/ig)) {
- engine = "presto"; // presto 内核
- supporter = "opera"; // opera 浏览器
- } else if (testUa(/trident|compatible|msie/ig)) {
- engine = "trident"; // trident 内核
- supporter = "iexplore"; // iexplore 浏览器
- }
内核版本 + 载体版本
- // 内核版本
- let engineVs = "unknow";
- if (engine === "webkit") {
- engineVs = testVs(/applewebkit\/[\d.]+/ig);
- } else if (engine === "gecko") {
- engineVs = testVs(/gecko\/[\d.]+/ig);
- } else if (engine === "presto") {
- engineVs = testVs(/presto\/[\d.]+/ig);
- } else if (engine === "trident") {
- engineVs = testVs(/trident\/[\d.]+/ig);
- }
- // 载体版本
- let supporterVs = "unknow";
- if (supporter === "chrome") {
- supporterVs = testVs(/Chrome\/[\d.]+/ig);
- } else if (supporter === "safari") {
- supporterVs = testVs(/version\/[\d.]+/ig);
- } else if (supporter === "firefox") {
- supporterVs = testVs(/Firefox\/[\d.]+/ig);
- } else if (supporter === "opera") {
- supporterVs = testVs(/opr\/[\d.]+/ig);
- } else if (supporter === "iexplore") {
- supporterVs = testVs(/(msie [\d.]+)|(rv:[\d.]+)/ig);
- } else if (supporter === "edge") {
- supporterVs = testVs(/edge\/[\d.]+/ig);
- }
外壳 + 外壳版本
- let shell = "none";
- let shellVs = "unknow";
- if (testUa(/micromessenger/ig)) {
- shell = "wechat"; // 微信浏览器
- shellVs = testVs(/micromessenger\/[\d.]+/ig);
- } else if (testUa(/qqbrowser/ig)) {
- shell = "qq"; // QQ 浏览器
- shellVs = testVs(/qqbrowser\/[\d.]+/ig);
- } else if (testUa(/ubrowser/ig)) {
- shell = "uc"; // UC 浏览器
- shellVs = testVs(/ubrowser\/[\d.]+/ig);
- } else if (testUa(/2345explorer/ig)) {
- shell = "2345"; // 2345 浏览器
- shellVs = testVs(/2345explorer\/[\d.]+/ig);
- } else if (testUa(/metasr/ig)) {
- shell = "sougou"; // 搜狗浏览器
- } else if (testUa(/lbbrowser/ig)) {
- shell = "liebao"; // 猎豹浏览器
- } else if (testUa(/maxthon/ig)) {
- shell = "maxthon"; // 遨游浏览器
- shellVs = testVs(/maxthon\/[\d.]+/ig);
- } else if (testUa(/bidubrowser/ig)) {
- shell = "baidu"; // 百度浏览器
- shellVs = testVs(/bidubrowser [\d.]+/ig);
- }
终极合体
根据以上的条件判断获得的变量如下, 我们可以把它们合并成一个对象输出. 这样就可以输出一个清晰的浏览器运行环境, 后面想干嘛就干嘛了, 多方便.
本文重点探究方案的可行性, 没有过多考虑到代码的优化, 所以条件判断使用得有些多, 如果有什么方法能优化下代码, 减少条件判断, 可以在下方评论提个建议哟.
system: 系统
systemVs: 系统版本
platform: 平台
engine: 内核
engineVs: 内核版本
supporter: 载体
supporterVs: 载体版本
shell: 外壳
shellVs: 外壳版本
- function BrowserType() {
- const ua = navigator.userAgent.toLowerCase();
- const testUa = regexp => regexp.test(ua);
- const testVs = regexp => (ua.match(regexp) + "").replace(/[^0-9|_.]/ig,"").replace(/_/ig, ".");
- // 接上以上 if...else 条件判断
- // ......
- // 获取到 system,systemVs,platform,engine,engineVs,supporter,supporterVs,shell,shellVs
- return Object.assign({
- engine, // webkit gecko presto trident
- engineVs,
- platform, // desktop mobile
- supporter, // Chrome Safari Firefox opera iexplore edge
- supporterVs,
- system, // Windows macos Linux Android iOS
- systemVs
- }, shell === "none" ? {} : {
- shell, // wechat qq uc 2345 sougou liebao maxthon baidu
- shellVs
- });
- }
在控制台执行 BrowserType(), 该有的都出来了, 哈哈! 源码详情请戳这里, 喜欢的可以点个赞支持下, 谢谢.
结语
写到最后总结得差不多了, 后续如果我想起还有哪些判断浏览器运行环境终极方案遗漏的, 会继续在这篇文章上补全, 同时也希望各位倔友对文章里的要点进行补充或者提出自己的见解. 欢迎在下方进行评论或补充喔, 喜欢的点个赞或收个藏, 保证你在开发时用得上.
来源: https://segmentfault.com/a/1190000020716933