前端兼容性问题 - CSS、JS 兼容

写在前面

前端最让人头疼的莫过于兼容性问题 ,由于操作系统 的不同以及操作系统版本 ,和浏览器内核 的不同以及浏览器版本不同,造成很多 CSS 样式、JS API 的兼容性问题。

主流移动设备

Android 设备情况 ,根据OpenSignal 在 2015 年 8 月发布的基础统计数据可以知道Andoroid的设备复杂度:ANDROID FRAGMENTATION VISUALIZED

iOS 设备情况 ,苹果公司已推出42款iPhone型号。维基百科 - iPhone 设备

主流浏览器

参考文章 OSCHINA - 2022 年 7 月浏览器市场份额 提供的数据:

中国 PC 端浏览器市场份额如下:

  • Chrome:35.47%
  • 360 Safe:18.76%
  • Edge:12.94%
  • Firefox:11.12%
  • QQ Browser:9.95%
  • Safari:3.92%

中国移动端浏览器市场份额如下:

  • Chrome:48.57%
  • Safari:21.9%
  • UC Browser:19.35%
  • QQ Browser:7.88%
  • Samsung Internet:0.73%
  • Android:0.53%

浏览器内核

浏览器内核包括浏览器的渲染引擎、JS引擎 ,不同的渲染引擎 对网页编写语法的解释不同,所以同一网页在不同内核的浏览器的渲染效果可能不同,而 JS 引擎 决定所有的 JS API 能不能使用,这就是浏览器内核造成兼容性问题的根本原因

国内主流浏览器的内核分别如下:

  1. Chrome:Chrome 浏览器使用 Blink 内核。Blink 是一个基于 WebKit 的开源渲染引擎,由 Google 主导开发。从 Chrome 28 版本开始,Chrome 放弃了 WebKit,改用 Blink 引擎
  2. 360 浏览器:使用的是基于 WebKit 内核开发的 X5 内核
  3. Edge:微软的 Edge 浏览器经历了两个版本的内核。从 2020 年开始,微软发布了基于 Chromium 项目的新版 Edge 浏览器,它使用了与 Chrome 相同的 Blink 内核
  4. Firefox:Firefox 浏览器使用名为 Gecko 的内核。Gecko 是 Mozilla 基金会开发的开源渲染引擎
  5. QQ 浏览器:使用的是基于 WebKit 内核开发的 X5 内核(现已升级至Blink)
  6. UC 浏览器:基于 WebKit 内核开发的 U3 内核【增加云端架构(实现压缩流量、加速加载功能)】
  7. Safari:使用的是 WebKit 的内核
  8. 百度浏览器:使用的是基于 WebKit 内核开发的 T5 内核
  9. Android 微信内置浏览器:微信5.4之前没有内置浏览器;微信5.4-6.1 (安装了QQ浏览器使用X5,未安装浏览器使用系统内核);腾讯TBSX5、微信自研XWeb(自2020年,现在大部分是这个)
  10. iOS 微信内置浏览器:UIWebView、WKWebView(2017年3月1日前逐步升级)

总结:

  • PC 端浏览器兼容问题重点关注 WebKit 内核和 Gecko 内核的兼容性问题
  • 移动端浏览器兼容问题重点关注 WebKit 内核

浏览器版本

随着 HTML5、CSS3、ES6 带来的新特性,新的浏览器版本会跟着支持这些新特性,但是老的浏览器版本以及迭代缓慢的浏览器(例如 IE)因为不支持这些新特性而引起兼容性问题

思考

我们要从这些角度去解决兼容性问题:

  1. 开发过程中先判断操作系统、浏览器内核及版本,写条件逻辑判断,以及利用一些业内广泛使用的能解决兼容性问题的工具去预防兼容性问题
  2. 测试过程中尽可能全面地进行真机调试或模拟器调试
  3. 在测试过程中发现并积累常见的兼容性问题,方便后面开发过程中提前避免问题

判断

如何判断操作系统

根据 navigator.userAgent 字段的值,对子字符串做匹配查找

js 复制代码
/** 判断客户端操作系统 */
export function judgeClient() {
  const userAgent = navigator.userAgent;
  let os = 'Unknown';
  
  if (userAgent.indexOf('Win') !== -1) {
    os = 'Windows';
  } else if (userAgent.indexOf('Mac') !== -1) {
    os = 'macOS';
  } else if (userAgent.indexOf('Linux') !== -1 || userAgent.indexOf('X11') !== -1) {
    os = 'Linux';
  } else if (/Android/.test(userAgent)) {
    os = 'Android';
  } else if (/iPhone|iPad|iPod/.test(userAgent)) {
    os = 'iOS';
  }
  
  return os;
}

如何判断浏览器类型、内核、版本

根据 navigator.userAgent 字段的值,对子字符串做匹配查找

js 复制代码
function judgeBrowser() {
  const userAgent = navigator.userAgent;
  let browser = 'Unknown';
  let engine = 'Unknown';
  let version = 'Unknown';

  if (userAgent.search(/MSIE/) != -1 || userAgent.search(/Trident/) != -1) {
    browser = 'Internet Explorer';
    engine = 'Trident';
    version = userAgent.match(/((MS)?IE\s|(\s?rv:))[0-9\.]+/)[0].replace(/[^\d.]/g, '');
  } else if (userAgent.search(/Firefox/) != -1) {
    browser = 'Firefox';
    engine = 'Gecko';
    version = userAgent.match(/Firefox\/([0-9\.]+)/)[1];
  } else if (userAgent.search(/Chrome/) != -1) {
    browser = 'Chrome';
    engine = 'WebKit';
    version = userAgent.match(/Chrome\/([0-9\.]+)/)[1];
  } else if (userAgent.search(/Safari/) != -1 && userAgent.search(/Chrome/) == -1) {
    browser = 'Safari';
    engine = 'WebKit';
    version = userAgent.match(/Version\/([0-9\.]+)/)[1];
  } else if (userAgent.search(/Opera/) != -1 || userAgent.search(/OPR/) != -1) {
    browser = 'Opera';
    engine = 'WebKit'; // 对于 Opera 15 及以上版本
    version = userAgent.match(/(Opera\/|Version\/|OPR\/)([0-9\.]+)/)[2];
  }

  return {
    browser: browser,
    engine: engine,
    version: version,
  };
}

虽然该函数识别了常见的浏览器类型、内核和版本,但为了支持更多浏览器或准确的版本信息,可以考虑使用更强大的库,例如 UAParser.js

开发中预防兼容性问题

Can I Use 网站

CSS、JS API 使用前先在 caniuse 网站查询是否支持

Autoprefixer 方案

业内广泛采用 autoprefixer 方案来自动根据浏览器内核添加前缀

在 webpack 中,可以通过安装 npm 包 postcss-loader 来启用 Autoprefixer

步骤如下:

  1. 安装 postcss-loader autoprefixer 依赖 运行 npm i postcss-loader autoprefixer -D 安装相关依赖
bash 复制代码
npm i postcss-loader autoprefixer -D
  1. 在项目根目录中创建postcss 的配置文件 postcss.config.js,并初始化如下配置
js 复制代码
const autoprefixer = require('autoprefixer');
 
module.exports = {
    plugins: [autoprefixer] // 挂载插件
}
  1. 在 webpack.config.js 的 module -> rules数组中,修改 css 的 loader 规则:
js 复制代码
rules: [
    {
        test: /\.css/,
        use: ['style-loader','css-loader','postcss-loader']
    }
]

重新运行项目即可

Babel 方案

业内广泛采用 Babel 方案 来将新版本 JavaScript (如 ES6、ES7、ES8 等)转换为 浏览器和其他环境中可执行的旧版本 JavaScript (如 ES5),以便能够运行在当前和旧版本的浏览器或其他环境中

Babel 的一些主要功能包括:

  1. 语法转换: 将新的、未广泛支持的 JavaScript 语法转换为旧的、更具兼容性的语法(如将箭头函数、类、async/await等转换为 ES5 等效版本)。
  2. Polyfills: 通过填充旧浏览器中缺少的 JavaScript API,使其支持一些新特性。例如,添加 Promise、Array.from 等新功能。这使新特性可在更旧的浏览器环境中使用。
  3. 插件: Babel 可以通过插件系统扩展,为原始 JavaScript 代码添加额外功能,如代码去除不使用的引入 ( tree shaking )、移除 console.log 语句压缩代码 等。
  4. 预设: Babel 预设是一组经过预先筛选的插件集合。将预设应用于 Babel 配置,方便快速配置 Babel 进行特定的转换任务。例如,使用 @babel/preset-env 将代码指定为匹配目标浏览器环境的版本。

以下是一个 Babel 配置示例(.babelrcbabel.config.json 文件),使用 @babel/preset-env 预设将代码转换为当前浏览器环境支持的版本:

json 复制代码
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "> 0.25%, not dead"
      }
    ]
  ]
}

在这个配置示例中,通过指定:要兼容 0.25% 及以上市场占有率的浏览器,排除已停止支持的浏览器。

这是语法转换的例子

js 复制代码
// Babel 接收到的输入是: ES2015 箭头函数
[1, 2, 3].map(n => n + 1);

// Babel 输出: ES5 语法实现的同等功能
[1, 2, 3].map(function(n) {
  return n + 1;
});

前言说到移动端设备型号复杂,尽管在开发过程中尽可能避免兼容性问题,但是还是很难预知所有的 CSS、JS API 在具体哪些设备不兼容,所以我们还需要把重心放在测试过程

如何测试兼容性问题

PC 端 Web 兼容性测试

在真实设备上进行测试

只有在真实的设备和操作系统上进行测试,才能提供最准确的结果。如果可能,获取并在所有目标操作系统和浏览器(包括不同版本)上进行测试

使用模拟器和虚拟机

如果无法获得所有真实设备,可以通过模拟器和虚拟机 进行测试。例如,可以使用 VirtualBoxVMware 设置虚拟机来模拟不同的操作系统 ,或使用如 Blisk 这样的开发者浏览器进行模拟

使用跨浏览器测试平台

这类服务提供了在多种真实设备和环境中进行在线测试的能力。例如,BrowserStackSauce Labs 提供了在真实设备和虚拟机上进行测试的云服务

使用自动化测试工具

通过使用自动化测试工具和框架(如 SeleniumCypressPuppeteer),可以自动地在各种环境下运行测试用例,从而提高测试效率

可访问性测试

使用如 LighthouseWebPageTest 进行可访问性测试,还可以进行性能测试

移动端 H5 兼容性测试

用户移动端设备复杂手机版本众多,所以移动端 H5 兼容性测试更加具有挑战性

真机或模拟器测试

这类测试是 CSS、JS API 兼容性测试的重点

  • 使用真实设备:将网页加载到不同类型的设备上进行测试,例如桌面电脑、笔记本电脑、平板电脑和智能手机等。
  • 使用模拟器和仿真器 :利用模拟器或仿真器来模拟不同设备的环境,并进行测试。常用的模拟器包括 Android Studio 自带的模拟器和 Xcode 中的 iOS 模拟器。

自动化测试工具

可以通过编写测试用例的方式,然后在跨平台、跨浏览器在各个真机上进行模拟测试,例如这些:

  • SeleniumSelenium 是一个流行的自动化测试框架,用于模拟用户在不同浏览器上的交互。它支持多种编程语言,并提供了丰富的API和工具,使开发者可以编写功能测试、回归测试和跨浏览器兼容性测试。
  • TestCafeTestCafe 是一款基于JavaScript的自动化测试工具,用于跨浏览器测试。它不需要额外的插件或驱动程序,能够在真实的浏览器中运行测试,并支持多个浏览器和平台。
  • CypressCypress 是另一个流行的自动化测试工具,专注于现代Web应用的端到端测试。它提供了简单易用的API,允许开发者在多个浏览器中运行测试,并具有强大的调试和交互功能。
  • BrowserStackBrowserStack 是一个云端跨浏览器测试平台,提供了大量真实浏览器和移动设备进行测试。它允许开发者在不同浏览器上同时运行测试,以检测网页在不同环境中的兼容性问题。

常见兼容性问题

CSS 兼容性问题

样式前缀

CSS 属性由于浏览器内核不同需要加不同的前缀进行兼容:

  1. Chrome(谷歌浏览器) 与 Safari(苹果浏览器) 内核:Webkit (中译无) 前缀:-webkit-
  2. IE (IE浏览器) 内核:Trident (中译三叉戟) 前缀:-ms-
  3. Firefox (火狐浏览器) 内核:Gecko(中译壁虎) 前缀:-moz-
  4. Opera (欧朋浏览器) 内核:Presto(中译迅速) 前缀:-o-

例如:

css 复制代码
-webkit-border-radius: 10px; /*谷歌浏览器*/
-ms-border-radius: 10px;     /*IE浏览器*/
-moz-border-radius: 10px;    /*火狐浏览器*/
-o-border-radius: 10px;      /*欧朋浏览器*/
border-radius: 10px; 

但为了解决兼容性问题 CSS 属性都这么写很费劲,所以我们采用上面说到的 Autoprefixer 自动添加浏览器前缀

flex 布局

覆盖率 98.14%

  • 6-9 版本的 IE
  • 10-11.5 版本的 Opera
  • 12 版本的 Opera Mobile

不被支持

解决:使用 Autoprefixer 自动添加浏览器前缀,确保 Flexbox 的兼容性。针对 IE10 和 IE11 特殊情况,还可考虑使用其他布局代替 Flexbox(如浮动、行内块等)

grid 布局

覆盖率:97.56%,在众多浏览器的较旧版本不被支持

解决:使用 Autoprefixer 添加前缀。必要时使用 Flexbox 或其他布局(如浮动、行内块等)代替

CSS 变量

在较旧的浏览器(如 IE11 及以下版本)中,CSS 变量(自定义属性)不被支持。

解决方法:在不支持 CSS 变量的旧浏览器中,可以使用预处理器(如 SassLess 等)实现类似的功能。另一个选择是使用 PostCSS 插件 postcss-custom-properties 将 CSS 变量编译为静态值

动画和过渡

CSS 动画和过渡在旧浏览器(如 IE9 及更早版本)中可能不受支持。此外,某些浏览器可能需要特定的前缀(如 -webkit-)才能识别动画和过渡属性。

解决方法:使用 Autoprefixer 自动处理浏览器前缀。针对不支持 CSS 动画和过渡的浏览器,可以实现回退方法(当动画和过渡不是关键内容时,可以仅使用静态表现形式),或使用 JavaScript 动画库(如 Animate.cssGSAP)实现兼容动画

JS API 兼容性问题

iOS 端获取时间异常

前端通过 new Date() 这个 JS API 来获取时间,但在部分 iOS 设备里如果传入格式为 "YYYY-MM-DD HH:mm:ss" 的日期字符串将会返回 NaN,如:

js 复制代码
const time = new Date("2023-12-06 12:00:00") 

原因是:部分 iOS 设备对于非标准日期字符串的解析相对较严格,认为 "YYYY-MM-DD HH:mm:ss" 这种格式不是日期时间,所以返回 NaN

解决方案:采用业界内广泛使用的 dayjs 来解决这个问题

步骤如下:

一、安装

js 复制代码
// CDN 资源
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
// NPM 安装
npm install dayjs

二、引入

js 复制代码
// ES 2015 之前
const dayjs = require('dayjs')
// ES 2015
import dayjs from 'dayjs'

三、使用

获取时间字符串,格式('YYYY-MM-DD HH:mm:ss')

js 复制代码
dayjs().format('YYYY-MM-DD HH:mm:ss') // 2023-12-04 21:03:24
dayjs(1701695087283).format('YYYY-MM-DD HH:mm:ss') // 2023-12-04 21:05:24

更多例子详见 dayjs

iOS 端音频播放问题

iOS 的 Safari 浏览器和 iOS 微信内置浏览器 都不支持 audio 的自动播放,解决方法是:

  • 微信内置浏览器调用微信的 WeixinJSBridgeReady 方法;
  • iOS的 safari 浏览器下,只能通过 ontouchstart 来触发自动播放
js 复制代码
function audioAutoPlay(id) {
    const audio = document.getElementById(id),
    const play = function() {
        audio.play();
        document.removeEventListener("touchstart",play,false);
    };
    audio.play();
    document.addEventListener("WeixinJSBridgeReady", function() {
        play();
    }, false);
    document.addEventListener('YixinJSBridgeReady', function() {
        play();
    }, false);
    document.addEventListener("touchstart", play, false);
}
audioAutoPlay('myAudio');

Promise

ES6 提供的 Promise 是异步编程的新特性。Promises 在一些较旧的浏览器上(如 IE11 及更早版本)不支持。解决方法:使用 polyfills,如 es6-promise

箭头函数

箭头函数(()=>{})在 IE11 及更早版本的浏览器中不受支持。解决方法:使用 Babel 将箭头函数转译成 ES5 函数,或使用传统函数语法

class(类)

ES6 引入了基于 class 关键字的面向对象编程。这个特性在 IE11 及更早版本的浏览器中不受支持。解决方法:使用 Babel 将 class 转换为 ES5 函数形式,或使用原型链继承的方式

Fetch API

作为 XMLHttpRequest 的现代替代方案,Fetch API 在较旧的浏览器(尤其是 Internet Explorer)中并不受支持。解决方法:考虑使用 whatwg-fetch 等 Polyfill 或使用 XMLHttpRequest

Object.assign

Object.assign() 用于将一个或多个对象的属性合并到目标对象。这个方法在 IE11 及更早版本不受支持。解决方法:使用 Polyfill,比如 object.assign

requestAnimationFrame

requestAnimationFrame 是一个进行高性能动画和游戏循环的函数。在一些旧版本浏览器中不支持。解决方案:实现 Polyfill,回退到 setTimeoutsetInterval

WebSocket

WebSockets 用于实现实时通信功能,在较旧的浏览器(如 IE9 及以下版本)中不被支持。解决方法:考虑保留策略,如轮询、长轮询。

参考文章

相关推荐
GIS程序媛—椰子27 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_00134 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端36 分钟前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x40 分钟前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
木舟100940 分钟前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43911 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
啦啦右一1 小时前
前端 | MYTED单篇TED词汇学习功能优化
前端·学习