浏览器兼容性问题总结
一、简介
浏览器内核主要包含两种引擎,一是渲染引擎,另一个是 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;
IOS
6.1 开始支持旧版本 display:-webkit-box;
7.1 开始支持标准版本display: flex;
PC
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 标准
}
else
{
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
Vue 就没打算支持 ie8
vue 不支持 IE8 及其以下版本,因为 Vue.js 使用了 IE8 不能模拟的 ECMAScript 5 特性,比如 Object.defineProperty()此方法就会报错,npm install --save-dev babel-polyfill
babel-polyfill 正确安装后,main.js 里引用:import "babel-polyfill";
四 参考文章:
来,让我们谈一谈 Normalize.css
JS 进阶篇--前端的瑞士军刀 Modernizr.js
CSS-宽度自适应和浏览器兼容笔记
IE 兼容性问题以及解决方案