浏览器兼容性问题总结

浏览器兼容性问题总结

一、简介

浏览器内核主要包含两种引擎,一是渲染引擎,另一个是 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 兼容性问题以及解决方案

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容