浏览器兼容性问题总结
浏览器内核主要包含两种引擎,一是渲染引擎,另一个是 js 引擎,常见的内核有 Trident(IE)、Gecko(Firefox 内核)、Blink(新的 chrome)、Webkit(Safari+老 chrome),国产的 360、猎豹、百度、QQ 浏览器大部分都是双内核(IE+Chrome)
在手机端 iso 系统都是内置的 WKWebview 内核,在安卓端内核差异较大,微信 6.1 版本以上的 android 用户,都是使用的 QQ 浏览器的 X5 内核(由开源 Webkit 优化的浏览器渲染引擎)。5.4-6.1 之间的版本,若用户安装了 QQ 浏览器就是使用的 X5 内核,若用户未安装浏览器,使用的是系统内核。
浏览器兼容问题主要分为样式兼容性(css)、交互兼容(js)、框架兼容(react、vue,主要是 babel 问题)。
二、css 兼容性问题
1、浏览器的初始化样式不同导致的兼容性问题
不同的浏览器默认很多样式不一致,最暴力的做法就是* {margin: 0; padding: 0;} 的方式,这种方式简单方便的一次性重置所有 HTML 网页元素的浏览器样式,但是也重置了很多有意义的默认样式。
Normalize.css 是研究了各种不同浏览器默认的差异,最终得出的,它的优势有:
保护有用的浏览器默认样式而不是完全去掉它们
一般化的样式:为大部分HTML元素提供
修复浏览器自身的bug并保证各浏览器的一致性
优化CSS可用性:用一些小技巧
解释代码:用注释和详细的文档来
Normalize.css 支持包括手机浏览器在内的超多浏览器,同时对 HTML5 元素、排版、列表、嵌入的内容、表单和表格都进行了一般化。
2、浏览器对 CSS3 的支持
浏览器前缀主要是解决各个浏览器的早期版本问题,通常,有 W3C 组织成员提出一个新属性,比如圆角 border-radius,大家都觉得好,但 W3C 制定标准,要走很复杂的程序。而浏览器商市场推广时间紧,如果一个属性已经够成熟了,就会在浏览器中加入支持。为避免日后 W3C 公布标准时有所变化,所以加入一个私有前缀.
-moz代表firefox浏览器私有属性
-ms代表IE浏览器私有属性
-webkit代表chrome、safari私有属性
-o代表opera私有属性
现在很多打包工具会解决浏览器前缀的问题,这个具体的往下看。
3、css hack
由于不同的浏览器和浏览器各版本对 CSS 的支持及解析结果不一样,以及 CSS 优先级对浏览器展现效果的影响,我们可以据此针对不同的浏览器情景来应用不同的 CSS;
CSS Hack 大致有 3 种表现形式:
CSS 属性前缀法
选择器前缀法
IE 条件注释法(即 HTML 头部引用 if IE)Hack
实际项目中 CSS Hack 大部分是针对 IE 浏览器不同版本之间的表现差异而引入的
只在IE下生效
<!--[if IE]>
这段文字只在IE浏览器显示
<![endif]-->
只在IE6下生效
<!--[if IE 6]>
这段文字只在IE6浏览器显示
<![endif]-->
只在IE6以上版本生效
<!--[if gte IE 6]>
这段文字只在IE6以上(包括)版本IE浏览器显示
<![endif]-->
只在IE8上不生效
<!--[if ! IE 8]>
这段文字在非IE8浏览器显示
<![endif]-->
非IE浏览器生效
<!--[if !IE]>
这段文字只在非IE浏览器显示
<![endif]-->
常见用法:
1、解决 ie9 以下浏览器对 html5 新增标签的不识别,并导致 CSS 不起作用的问题
html5shiv
<!--[if lt IE 9]>
<script type="text/javascript" src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<![endif]-->
2、让不支持 css3 Media Query 的浏览器包括 IE6-IE8 等其他浏览器支持查询
html5shiv
<!--[if lt IE 9]>
<script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
4、Modernizr 使用
Modernizr 默认做的事情很少,除了(在你选择的情况下)给不支持 html5 的标签的浏览器,如 IE6,7,8 追加一点由 Remy Sharp 开发的 html5 垫片脚本,使其识别<aside>、<section>等 html5 元素之外,它主要做的就是浏览器‘功能检测’。它知道浏览器是否支持各种 html5 和 css3 特性。
.boxshadow #MyContainer {
border: none;
-webkit-box-shadow: #666 1px 1px 1px;
-moz-box-shadow: #666 1px 1px 1px;
.no-boxshadow #MyContainer {
border: 2px solid black;
因为如果浏览器支持 box-shadows 的话,Modernizr 就会将 boxshadow class 添加到<html>元素,然后你可以将它管理到一个相应的 div 的 id 上。如果不支持,Modernizr 就会将 no-boxshadow class 添加到<html>元素,这样显示的就是一个标准的边框。这样我们就可以很方便地在支持 CSS3 特性的浏览器上使用 CSS3 新功能,不支持的浏览器上继续使用以前的方式。
5、自动化工具
Autoprefixer
Autoprefixer 是一个流行的 PostCSS 插件,其作用是为 CSS 中的属性添加浏览器特定的前缀。由于 CSS 规范的制定和完善一般需要花费比较长的时间,浏览器厂商在实现某个 CSS 新功能时,会使用特定的浏览器前缀来作为正式规范版本之前的实验性实现。比如 Webkit 核心的浏览器使用-webkit-,微软的 IE 使用-ms-。为了兼容不同浏览器的不同版本,在编写 CSS 样式规则声明时通常需要添加额外的带前缀的属性。这是一项繁琐而无趣的工作。Autoprefixer 可以自动的完成这项工作。Autoprefixer 可以根据需要指定支持的浏览器类型和版本,自动添加所需的带前缀的属性声明。
在 react 和 vue 项目配置中一般会以 browserslist 属性配置在 package.json 中,browserslist 字段会被 @babel/preset-env 和 Autoprefixer 用来确定需要转译的 JavaScript 特性和需要添加的 CSS 浏览器前缀,一般搭配:
Autoprefixer
Babel
postcss-preset-env
eslint-plugin-compat
stylelint-no-unsupported-browser-features
postcss-normalize
6、常见问题
a、flex 布局兼容问题
flex 布局分为旧版本 dispaly: box;,过渡版本 dispaly: flex box;,以及现在的标准版本 display: flex;
Android
2.3 开始就支持旧版本 display:-webkit-box;
4.4 开始支持标准版本 display: flex;
6.1 开始支持旧版本 display:-webkit-box;
7.1 开始支持标准版本display: flex;
ie10开始支持,但是IE10的是-ms形式的。
b、 placeholder 在 IE9 及以前的浏览器中不能被支持,另外还有有些样式兼容性问题
<input type="text" value="Name *" onFocus="this.value = '';" onBlur="if (this.value == '') {this.value = 'Name *';}">
c、IE9 以下浏览器不能使用 opacity
opacity: 0.5;
filter: alpha(opacity = 50);
filter: progid:DXImageTransform.Microsoft.Alpha(style = 0, opacity = 50);
js 兼容性
js 兼容性问题主要分为 2 类,第一类是不同浏览器对于 es5 语法的兼容性,第二类是对有些新的语法(ES6,ts)兼容性问题。
1、shim
一些 shim 库使用老旧浏览器下的 API,实现现代浏览器的 API,帮助开发者抹平浏览器环境的差异。这个库中的方法接收的参数与调用方法与标准的方法一样,但是 shim 中的方法是自己实现逻辑处理的,因此在方法中加入了兼容性处理。所以方法的返回结果与标准方法相同。
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.7/es5-shim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.7/es5-sham.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.34.2/es6-shim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.34.2/es6-sham.min.js"></script>
<script src="https://wzrd.in/standalone/es7-shim@latest"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json2/20160511/json2.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.min.js"></script>
这些 shim 可以通过 IE 的条件注释,按需引入。
<!--[if lt IE 9]>
<script src="es5-shim/es5-shim.min.js"></script>
<script src="es5-shim/es5-sham.min.js"></script>
<script src="/json2/json2.min.js"></script>
<![endif]-->
2、polyfill.js
一个 shim 是一个库,它将一系列新的、标准的 API 引入到一个旧的环境中,而且仅靠旧环境中已有的手段实现旧版兼容。例如我们经常听到的 es5-shim,它是在 es3 的引擎上实现了 es5 的特性,但用到的都是 es3 的技术,而且在 Node.js 上和在浏览器上有完全相同的表现,所以它是 shim。
polyfill 是解决跨浏览器 API 兼容性问题的 shim,相当于一段代码。
我们通常的做法是先检查当前浏览器是否支持某个 API,如果不支持的话就加载对应的 polyfill,然后新旧浏览器就都可以使用这个 API 了。
怎样区分 polyfill 和 shim?
如果浏览器 X 支持标准规定的功能,那么 polyfill 可以让浏览器 Y 的行为与浏览器 X 一样。
而前面所说的 es5-shim 在 es3 的引擎上实现了 es5 的特性,但用到的都是 es3 的技术,而且在 Node.js 上和在浏览器上有完全相同的表现,所以它是 shim。
3、babel-polyfill
Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,
以及一些定义在全局对象上的方法(比如 Object.assign)都不会转码。
举例来说,ES6 在 Array 对象上新增了 Array.from 方法。Babel 就不会转码这个方法。如果想让这个方法运行,必须使用 babel-polyfill,为当前环境提供一个垫片。
在实际应用场景下,整个 @babel/polyfill 体积较大。我们可以配置 .babelrc 来按需加载
"presets": [["@babel/preset-env", {
"useBuiltIns": "usage", //如果我们配置了该项 就不需要在webpack中配置entry了
"corejs": 3
4、es3ify-loader
老旧的 JavaScript 引擎在对象声明或属性访问表达式中,不支持保留字,主要作用在 IE8 的兼容性问题
5、常见原生 js 兼容性问题
a、事件绑定的兼容问题;
var eventshiv = {
// event兼容
getEvent: function(event) {
return event ? event : window.event;
// type兼容
getType: function(event) {
return event.type;
// target兼容
getTarget: function(event) {
return event.target ? event.target : event.srcelem;
// 添加事件句柄
addHandler: function(elem, type, listener) {
if (elem.addEventListener) {
elem.addEventListener(type, listener, false);
} else if (elem.attachEvent) {
elem.attachEvent('on' + type, listener);
} else {
// 在这里由于.与'on'字符串不能链接,只能用 []
elem['on' + type] = listener;
// 移除事件句柄
removeHandler: function(elem, type, listener) {
if (elem.removeEventListener) {
elem.removeEventListener(type, listener, false);
} else if (elem.detachEvent) {
elem.detachEvent('on' + type, listener);
} else {
elem['on' + type] = null;
// 添加事件代理
addAgent: function (elem, type, agent, listener) {
elem.addEventListener(type, function (e) {
if (e.target.matches(agent)) {
listener.call(e.target, e); // this 指向 e.target
// 取消默认行为
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
// 阻止事件冒泡
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
b、键盘事件 keyCode 兼容性写法;
var inp = document.getElementById('inp')
var result = document.getElementById('result')
function getKeyCode(e) {
e = e ? e : (window.event ? window.event : "")
return e.keyCode ? e.keyCode : e.which
inp.onkeypress = function(e) {
result.innerHTML = getKeyCode(e)
c、获取页面滚动值 scrollTop 的兼容问题。一些浏览器将 scrollTop 绑定在 body 上,还有一些绑定在 HTML 上,解决办法;
var scrollTop = document.body.scrollTop || document.documentElement.scroll
d、阻止事件传播和默认事件;
阻止事件传播:
btn.onclick = function(event) {
alert("点击了按钮")
var event = event || window.event;
if(event && event.stopPropagation)
event.stopPropagation(); // w3c 标准
event.cancelBubble = true; // ie 678 ie浏览器
//js阻止默认事件
document.onclick=function(e){
var e=e||window.event;
if (e.preventDefault) {
e.preventDefault();//W3C标准
}else{
e.returnValue='false';//IE..
三、框架兼容性
react
React 早在 0.14.x 版本就抛弃了对 ie8 的支持。
1、ES6 兼容性问题
由于 Babel 默认只转换转各种 ES2015 语法,而不转换新的 API,比如 Promise,以及 Object.assign、Array.from 这些新方法,这时我们需要提供一些 ployfill 来模拟出这样一个提供原生支持功能的浏览器环境。
主要有两种方式:babel-runtime 和 babel-polyfill。
A.babel-runtime
babel-runtime 的作用是模拟 ES2015 环境,包含各种分散的 polyfill 模块,我们可以在自己的模块里单独引入。
B.babel-polyfill
而 babel-polyfill 是针对全局环境的,引入它浏览器就好像具备了规范里定义的完整的特性,一旦引入,就会跑一个 babel-polyfill 实例。
2.react 对低版本的安卓 webview 兼容性
A.android 较低版本 webview 不支持 Object.assign 改用 var objectAssign = require('object-assign’) 这种情况上面方案可以解决
B.import React from 'react';import ReactDOM from 'react-dom';//不可放在其他模块引入的后面,否则 android5.0 及以下版本 webview 报错
3、create-react-app 提供的 react-app-polyfill 只是对某些 ES6 的 API 做了兼容
所以用 @babel/polyfill 替代了 react-app-polyfill
4、dev 环境不支持 IE9
在 IE9 上打开 dev 环境发现了很多问题,不仅有 webpack-dev-server 存在的兼容性问题,还有一些其它第三方库存在的兼容性问题,主要有以下问题(因为问题太多所以最后我放弃了 IE9 下的 dev 环境):
首先是 bundle.js 文件里报 const map 未定义是因为 webpack-dev-server 从 2.7.1 后不支持 IE9
如果是 vendor.js 文件里报 setPrototypeOf 未定义是因为 create-react-app 引入的 react-dev-utils/webpackHotDevClient 文件里面有一个 chalk 的包使用了这个 ES6 的方法,而 @babel/polyfill 的编译是基于proto,而 IE9 并不支持proto,所 以@babel/polyfill 无效
5、IE9 控制台报错 TYPEError Cannot call a class as a function
这是由于 babel 使用的 setPrototypeOf 方法的 polyfill 内部使用了 proto 去实现而 IE9 不支持,所以可以用 setprototypeof 来模拟,记住要在页面加载时最先加载。
IE10 以下在 constructor 里面调用 this.props 会返回 undefined
建议直接使用 constructor 传递的 props
Vue 就没打算支持 ie8
vue 不支持 IE8 及其以下版本,因为 Vue.js 使用了 IE8 不能模拟的 ECMAScript 5 特性,比如 Object.defineProperty()此方法就会报错,npm install --save-dev babel-polyfill
babel-polyfill 正确安装后,main.js 里引用:import "babel-polyfill";