前端系列之:兼容性
一、为什么会有兼容性问题
产生兼容性问题的根本原因在于各浏览器的内核不同。
浏览器内核又可以分成两部分:
-
渲染引擎:负责渲染 HTML、CSS
-
JavaScript 引擎:解析 JavaScript
不同浏览器内核支持的 HTML、CSS、JavaScript 等网页语言标准不同,以及用户客户端的环境不同(如分辨率不同)造成了显示效果不能达到理想效果。
1.1、浏览器 - 内核对应关系
| 浏览器 | 内核 | 历史 |
|---|---|---|
| Safari [səˈfɑːri] | WebKit | 由苹果公司开发给 Safari 浏览器使用的内核 |
| Chrome | Blink | 最开始使是 Chromium 内核,它源自 WebKit,后来,由于苹果推出的 WebKit2 与 Chromium 的沙箱设计存在冲突,所以 Chromium 一直停留在 WebKit,最后 Google 决定从 WebKit 衍生出自己的 Blink 引擎(由 Google 和 Opera Software 共同研发) |
| Opera | Blink | 最开始使用的是由 Opera 公司自己研发的 Presto 内核,后面跟随 Chrome 先使用 Webkit,现在使用 Blink |
| Firefox | Gecko [ˈɡekəʊ] | |
| IE | Trident ['traɪd(ə)nt] | |
| Edge | 基于 Chromium 内核 [ˈkroʊmiəm] |
国产浏览器一般采用 Trident + WebKit 的双内核模式,切换到兼容模式时使用 Trident 内核,切换到极速模式时使用 WebKit 内核。
二、解决兼容性问题的通用策略
2.1、设计理念
渐进增强 (Progressive Enhancement) 与优雅降级 (Graceful Degradation)
-
渐进增强: 先为所有浏览器提供基本功能,再为高级浏览器添加增强功能。
-
优雅降级: 先为高级浏览器提供完整体验,再为低级浏览器提供可接受的降级方案。
2.2、技术实现方法
-
CSS Reset / Normalize.css:统一基础样式。
-
特性检测代替浏览器嗅探
-
特性检测:通过判断浏览器是否支持某一特性(如 if (window.Promise))决定代码执行,这种方法更可靠,不依赖易伪造的 navigator.userAgent。
-
避免浏览器嗅探:不推荐通过解析用户代理字符串实现兼容,因其易于被篡改且难以维护。
-
-
Polyfill ['pəʊliːfɪl]
- 通过 Polyfill(如 core-js)在旧版浏览器中模拟实现新的 JavaScript API,使其支持现代特性。
-
CSS 预处理与后处理工具
-
使用 Sass、Less 等预处理器提升 CSS 的可维护性和结构化程度。
-
借助 PostCSS 和 Autoprefixer 等后处理工具自动添加兼容性前缀,简化样式兼容工作。
-
-
兼容性查询网站
- Can I use 查询 HTML5、CSS3、JavaScript API 在不同浏览器版本中的支持情况。
2.3、开发工具与框架
-
Babel 与 PostCSS:通过转换和插件自动处理 JavaScript 和 CSS 的语法兼容问题。
-
现代前端框架:React、Vue、Angular 等框架内置了许多兼容性处理机制,提供了跨浏览器一致的开发体验。
2.4、测试
-
跨浏览器测试:在不同浏览器和设备上进行实际测试。
-
自动化测试工具:使用 BrowserStack、Sauce Labs 等云测试平台,或 Puppeteer、Selenium 等工具进行自动化测试。
三、CSS 兼容性
3.1、为什么 CSS 需要前缀
当浏览器厂商实现新的 CSS 特性时,这些特性通常还没有被标准化。
标准化意味着该特性已经被所有浏览器一致认可并且没有可能被修改或废弃。
非标准化则意味着不同浏览器可能对一个新特性有不同的理解和实现方式,导致它们在不同浏览器中的表现不一致。
所以,在新特性刚刚推出时,浏览器厂商不会直接将它作为标准特性来实现,而是先进行实验和测试。
为了让开发者可以提前使用这些实验性特性,浏览器厂商会给它们加上前缀。加前缀的目的是为了区分这些实验性的、可能会发生变化的特性和已经标准化的特性。
早期的浏览器版本在引入新特性时,由于当时这些特性还没有被标准化,所以需要使用前缀来区分它们是实验性特性。
随着时间的推移,许多 CSS 特性已经被正式标准化,那么开发者就不需要加前缀了。
但是为了兼容一些老版本的浏览器,还是会需要编写一些带前缀的 CSS。
| 前缀 | 内核 | 浏览器 |
|---|---|---|
| -webkit- | WebKit、Blink | Safari, Chrome, Edge |
| -moz- | Gecko | Firefox |
| -o- | Presto | 旧版 Opera |
| -ms- | Trident | IE |
有哪些新特性需要加前缀?
例如一些 CSS3 的新特性:
flex 布局、grid 布局、box-shadow、transform 等等。
3.2、为什么要清除默认样式
不同浏览器的标签默认的内外边距不同,例如两个紧挨着的 div,有的浏览器它们会紧密贴合,有的浏览器它们之间会有间距,所以需要自己定义默认样式。
这里给出一份清除默认样式的重置样式代码:
/*** 该文件用于清除浏览器样式*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {padding:0;margin:0;border:0;outline: 0;font-family: "Helvetica Neue For Number", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;word-wrap:break-word;
}
html, body {width: 100%;height: 100%;
}
a {text-decoration: none;-webkit-tap-highlight-color:rgba(255,255,255,0);
}
ul, ol {list-style-type: none;
}
textarea {resize: none;
}
/*去除input button默认样式*/
input, button, textarea {-webkit-appearance: none;-webkit-tap-highlight-color: rgba(255, 225, 225, 0);padding: 0;border: 0;outline: 0;
}
// 修改placeholder属性默认文字颜色
input::-webkit-input-placeholder, textarea::-webkit-input-placeholder {/* WebKit browsers */color: #999;
}
input:-moz-placeholder, textarea:-moz-placeholder {/* Mozilla Firefox 4 to 18 */color: #999;
}
input::-moz-placeholder, textarea::-moz-placeholder {/* Mozilla Firefox 19+ */color: #999;
}
input:-ms-input-placeholder, textarea:-ms-input-placeholder {/* Internet Explorer 10+ */color: #999;
}
除了自己定义清除默认样式的代码,也可选择引用别人写好的成熟插件 Reset.css 或 Normalize.css 来清除默认样式。
-
CSS Reset:重置所有元素的默认样式为统一值。
-
Normalize.css:保留有用的浏览器默认样式,只对不一致的样式进行标准化。相比 Reset 更温和。
3.3、响应式布局兼容性
3.3.1、媒体查询兼容性
兼容 IE9+ 及现代浏览器,IE8 及以下不兼容。
使用媒体查询的核心策略是移动优先(Mobile First),即通常从 min-width 开始,先为小屏幕设计基础样式,然后逐步为更大的屏幕添加或覆盖样式。
/* 移动优先策略 */
.container { width: 100%; }/* 平板及以上 */
@media (min-width: 768px) {.container { width: 750px; }
}/* 桌面及以上 */
@media (min-width: 992px) {.container { width: 970px; }
}
注意:使用 rem 单位的媒体查询可以更好地适应浏览器字体大小的调整,更具可访问性。
3.3.2、flex 兼容性
兼容现代浏览器,IE10 需加前缀。
3.3.3、grid 兼容性
兼容现代浏览器,IE11 需加前缀。
对于不兼容的浏览器可以采取哪些措施呢?
答案:优雅降级与渐进增强。
方案一:
/* 降级方案 - 使用 Flexbox (IE11 支持) */
.container {display: flex;flex-wrap: wrap;
}
.item {width: 33.33%; /* 三列布局 */
}/* 渐进增强 - 使用 Grid (现代浏览器) */
@supports (display: grid) {.container {display: grid;grid-template-columns: repeat(3, 1fr); /* fr 单位 IE 不支持 */gap: 20px; /* gap 属性 IE 不支持 */width: auto; /* 覆盖之前的宽度 */}.item {width: auto; /* 覆盖之前的宽度 */}
}
由于 CSS 的层叠特性,支持 Grid 的浏览器会采用 Grid 样式,覆盖掉之前的降级样式。
方案二:
.container {display: block; /* 最基础的布局 */
}@media (min-width: 568px) {.container {display: flex; /* 中等屏幕用 Flexbox */flex-wrap: wrap;}.item { width: 50%; }
}@media (min-width: 1024px) {.container {display: grid; /* 大屏幕用 Grid,支持 Grid 的浏览器会覆盖 display: flex */grid-template-columns: repeat(3, 1fr);gap: 20px;}.item { width: auto; }
}
3.3.4、fixed 兼容性
早期的浏览器使用 position: fixed 是可能会出现闪烁、跳跃的情况,但是现代浏览器已经可以放心使用了。
为了避免一些不必要的麻烦,使用 position: fixed 时可以遵循以下规则:
-
避免父级滚动容器影响:
可以将 fixed 元素直接作为
<body>的子元素,避免嵌套在带 overflow:scroll 或 -webkit-overflow-scrolling: touch 的容器中。 -
避免在 fixed 元素的祖先节点上使用 transform、perspective 或 filter
3.4、选择器兼容性
避免使用过于复杂的选择器,尽量使用普通的类名、ID 或标签名进行选择。例如 IE8 及以下就不能识别伪类选择器。
其实只要不考虑 IE,很多浏览器兼容性问题都将不存在。
3.5、其他的一些兼容性问题
3.5.1、Chrome 中字体不能小于 12px
解决方案:
div {font-size: 12px;transform: scale(0.8);
}
3.5.2、Chrome 中密码后输入框背景色为黄色
解决方案:
input{background-color: transparent !important;
}
input:-webkit-autofill, textarea:-webkit-autofill, select:-webkit-autofill {-webkit-text-fill-color: #333 !important;-webkit-box-shadow: 0 0 0 1000px transparent inset !important;background-color: transparent !important;background-image: none !important;transition: background-color 5000s ease-in-out 0s;
}
3.5.3、相邻元素设置 margin 边距时,margin 将取最大值,舍弃小值
<div><div class="box1">50px</div><div class="box2">100px</div>
</div><style scoped>
.box1 {margin: 50px;background-color: red;padding: 10px;border: 1px solid black;
}.box2 {margin: 100px;background-color: aqua;padding: 10px;border: 1px solid black;
}
</style>

按常规思维 box1 与 box2 的边距应该是 50px + 100px = 150px,但是实际是只有 100px。
解决方案:给其中一个 box 加上父级,并给父级设置 overflow: hidden。或者只设置一个 margin。
<div class="box1">50px</div>
<div style="overflow: hidden;"><div class="box2">100px</div>
</div>
或
.box1 {margin: 50px 50px 150px 50px;background-color: red;padding: 10px;border: 1px solid black;
}.box2 {margin: 0 100px 100px 100px;background-color: aqua;padding: 10px;border: 1px solid black;
}
3.5.4、IE9 以下浏览器不能使用 opacit
解决方案:使用 filter 代替。
opacity: 0.5; /* 现代浏览器 */filter: alpha(opacity = 50); /* IE6-IE8 */
更多 CSS 知识可以参考文章:CSS 系列之:基础知识
四、JavaScript 兼容性
4.1、ES6+ 兼容性
问题:
- 箭头函数、let/const、Promise、async/await、Class 等 ES6+ 语法和 API 在旧版浏览器中不支持。
解决方案:
-
Babel: 将 ES6+ 代码转换(transpile)为 ES5 兼容的代码。
-
Polyfill (垫片) : 使用 core-js 等库为旧浏览器提供新 API 的实现。通常与 Babel 结合使用 (@babel/polyfill 或 @babel/preset-env 的 useBuiltIns 选项)。
4.2、DOM 操作差异
问题:
-
事件监听:IE 早期版本使用 attachEvent(),现代浏览器使用 addEventListener()。
-
文本内容:IE 使用 innerText,现代浏览器使用 textContent 性能更好。
-
获取样式:IE 使用 currentStyle,现代浏览器使用 getComputedStyle()。
解决方案:
-
统一封装函数: 编写兼容性函数来处理这些差异。
-
使用现代前端框架/库: React, Vue, Angular, jQuery
4.3、XMLHttpRequest 兼容性
问题:
- 旧版本的 IE 浏览器(< IE7)使用 ActiveX 对象而不是 XMLHttpRequest。
解决方案:
-
检查浏览器是否支持原生的 XMLHttpRequest 对象,如果不支持,则使用 ActiveX 对象作为替代方案。
-
使用 Axios、Fetch 等 HTTP 库: 这些库内部已经处理了 XHR 的兼容性,并提供了统一的 API。
4.4、LocalStorage/SessionStorage
问题:
- 旧版浏览器(如 IE7 及更早)不支持 localStorage 和 sessionStorage。
解决方案:
- 降级处理:在使用前判断 window.localStorage 是否存在,如果不存在,则使用 Cookie 或其他存储方式作为备用。
4.5、JSON 解析兼容性
旧版本的浏览器可能不支持JSON.parse()和JSON.stringify()方法。
使用 json2.js 等 JSON 解析库来提供对这些方法的支持,或者在必要时手动实现 JSON 的解析和序列化功能。
4.6、音视频兼容性
主要有音频格式、自动播放、循环播放等问题。
五、移动端兼容性
5.1、移动端 1px 边框
解决方案:伪元素 + transform。
原元素的 border 去掉,然后利用 :before 或者 :after 设置 height 或 width,并使用 transform 的 scale 缩小一半,原元素设置 position: relative,:before 或 :after 中使用 position: absolute。
<div class="one-pixel-border">50px</div><style scoped>
.one-pixel-border {position: relative;width: 100px;height: 50px;border-radius: 8px;/* 圆角 */
}.one-pixel-border::after {content: '';position: absolute;top: 0;left: 0;/* 表示这个伪元素的宽高是父元素的 2 倍 */width: 200%;height: 200%;border: 1px solid #000;/* 放大后的圆角 = 原始圆角 * 2 */border-radius: 16px;/* 整体缩小一半,1 */transform: scale(0.5);/* 设置缩放的原点为左上角(0,0),确保缩放后元素从左上角对齐,不会偏移 */transform-origin: 0 0;pointer-events: none;/* 防止伪元素干扰点击 */
}
</style>
5.2、视口 (Viewport) 设置
问题:
- 移动端浏览器默认会以桌面模式渲染页面,导致页面过小或需要横向滚动。
解决方案:
-
在
<head>中的<meta>设置视口元标签,控制页面在移动设备上的布局和缩放。<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
5.3、软键盘弹出问题
问题:
- 移动端输入框聚焦时弹出软键盘,可能导致页面布局错乱、position: fixed 元素位置异常。
解决方案:
-
监听 resize 事件: 在软键盘弹出时(window.innerHeight 变化),调整页面布局。
-
避免在输入框聚焦时使用 position: fixed 的元素: 或者在软键盘弹出时隐藏这些元素。
-
使用 scrollIntoView() : 确保输入框在软键盘弹出后仍然可见。
六、HTML 兼容性
主要是 HTML5 新增的一些 <header>、<nav>、<section>、<article>、<footer> 等 语义化标签,以及 <canvas>、<video>、<audio> 等功能标签,在一些旧浏览器会将其解析为 “未知的行内元素”。
HTML5 新增了 <input type="email">(邮箱输入)、<input type="date">(日期选择)、<input type="range">(滑块)等增强型表单控件旧浏览器将这些控件降级为普通文本框(<input type="text">),且不同现代浏览器对这些控件的默认样式差异大。
七、图片兼容性
JavaScript 系列之:图片压缩
八、工具
8.1、Babel
Babel 是一个广泛使用的 JavaScript 编译器/转译器,其核心作用是将高版本 JavaScript(如 ES6+)转换为向后兼容的低版本代码(如 ES5),以确保代码能在旧版浏览器或环境中正常运行。
Babel的主要作用:
-
语法转换:
将现代 JavaScript 语法(如 let/const、箭头函数、类、模板字符串、解构赋值等)转换为等价的 ES5 语法,以便在不支持新特性的浏览器中运行。
-
按需转换:
结合 browserslist 配置,@babel/preset-env 可根据指定的目标浏览器自动决定哪些特性需要转换,哪些可以保留原样。
-
Polyfill 填充新 API:
通过插件 core-js,为旧环境提供对新增全局对象(如 Promise、Array.from、Map、Set)的支持。
-
支持 TypeScript 和 React 的 JSX 语法。
使用方法:
-
npm install --save-dev @babel/core @babel/cli @babel/preset-env -
在项目的根目录中创建名为 babel.config.js 的配置文件:
const presets = [["@babel/preset-env",{targets: {edge: "17",firefox: "60",chrome: "67",safari: "11.1",},useBuiltIns: "usage",corejs: "3.6.4",},], ];module.exports = { presets };
更多详细内容可阅读 babel 中文文档:https://babel.docschina.org/docs
8.2、core-js
core-js 是一个 JavaScript 的 polyfill 解决方案,它为不同 JavaScript 环境提供了符合 ECMAScript 规范的底层 API 实现。简单来说,core-js 让开发者能够在旧版浏览器或环境中使用最新的 JavaScript 特性。
core-js 主要解决三个核心问题:
-
填补浏览器/环境缺失的标准 API
-
实现最新的 ECMAScript 提案特性
-
提供模块化的按需加载能力
core-js 的核心工作流程:
core-js 通常与 Babel 一起使用,形成完整的转译方案:
// babel.config.js
module.exports = {presets: [['@babel/preset-env', {useBuiltIns: 'usage', // 按需加载corejs: 3 // 指定 core-js 版本}]]
};
8.2.1、Babel 与 core-js 的关系
Babel 负责语法转译,core-js 负责 API 填充,两者互补。它们是独立的库:Babel 不内置 core-js 功能,core-js 也不内置 Babel 功能。但是Babel 通过 @babel/preset-env 的 useBuiltIns 选项来集成 core-js。
早期 Babel 提供 @babel/polyfill(封装 core-js 和 regenerator-runtime),但从 7.4.0 起已被废弃,官方推荐直接使用 core-js。
为什么是在 Babel 中配置 core-js,而不是在 core-js中配置 Babel?
Babel 的工作流程可以概括为:
-
解析你的代码,生成抽象语法树(AST)。
-
遍历 AST,并应用各种插件来转换代码。
-
生成新的、兼容性更好的代码。
在第 2 步中,当 Babel 的插件(如 @babel/preset-env)遇到一个它无法通过语法转换来解决的新 API(如 new Map())时,它需要做两件事:
-
判断:根据你配置的 浏览器目标(targets),判断当前目标环境是否原生支持这个 API。
-
操作:如果不支持,它就需要从某个地方(即 core-js)引入这个 API 的 polyfill 实现,并替换或注入到你的代码中。
这个“判断”和“引入”的动作,必须由 Babel 来发起。core-js 只是一个被动的代码库,它不知道自己应该被用在何处、何时、以及用什么方式被引入。
而且因为有了 Babel 的判断,就无需引入整个 core-js 库,这样会引入大量冗余代码,因为你的目标浏览器可能已经支持了大部分 API。
8.3、PostCSS
PostCSS 是一个平台,本身不直接提供功能,只提供基础设施,是通过丰富的插件生态系统来实现各种功能。
PostCSS 核心工作流程:
-
输入:你编写 CSS 文件(可以是普通的 CSS,也可以是使用未来语法的 CSS)。
-
处理:PostCSS 解析你的 CSS,将其转换为一个抽象的语法树(AST)。然后,各种插件在这个 AST 上进行操作和转换。
-
输出:处理后的 AST 被重新转换为字符串,输出为最终的 CSS 文件。
这个过程通常通过构建工具(如 Webpack、Gulp、Vite 等)自动继承。
使用方法:
-
安装依赖:
# autoprefixer 是浏览器兼容性插件 # cssnano 是 css 优化与压缩插件 npm install postcss autoprefixer cssnano--save-dev -
项目根目录创建 postcss.config.js:
module.exports = {plugins: {autoprefixer: {}, // autoprefixer 插件,使用默认配置cssnano: {} // cssnano 插件,使用默认配置} }或
module.exports = {plugins: [require('autoprefixer'), // 加载 autoprefixerrequire('cssnano') // 加载 cssnano] };两种写法都行。
8.3.1、PostCSS 与 Autoprefixer
Autoprefixer 是 PostCSS 的一款兼容性处理插件。
核心功能是:根据目标浏览器的支持情况,自动为 CSS 属性添加必要的浏览器前缀。
工作原理:
-
读取配置:通过 browserslist 配置(如 last 2 versions“最近 2 个版本”、> 1%“覆盖全球 1% 以上用户的浏览器”)确定目标浏览器。
-
匹配规则:查询 Can I Use 数据,判断当前 CSS 属性在目标浏览器中是否需要前缀。
-
补充前缀:对需要前缀的属性自动添加对应前缀,同时保留标准属性(如同时输出 display: flex 和 display: -webkit-box)。
Autoprefixer 为什么不自己单干,反而要依赖 PostCSS?
解析 CSS、生成 AST、遍历节点、生成新代码,这些是几乎所有 CSS 处理工具都需要做的底层、通用且复杂的工作。PostCSS 作为平台已经实现了这些基础功能,Autoprefixer 依赖 PostCSS 则可以让自己只关注添加前缀这一核心功能。
8.3.2、PostCSS 与 Sass/Less
Sass 和 Less 也是前端中经常使用的 CSS 工具,他们不是 PostCSS 的插件,他们是 CSS 预处理器,扩展了原生 CSS 的功能,例如变量、嵌套、混合等功能。
浏览器只能识别标准 CSS 语法,而预处理器提供的语法(如变量、嵌套、混合等)是浏览器不认识的。因此,必须在代码被浏览器加载之前,通过预处理器将这些扩展功能提前转换为标准 CSS,所以叫做预处理器。
8.4、browserslist
Browserslist 有两种常见方式:
-
在 package.json 相应字段中增加
"browserslist": ["> 1%","last 2 versions","not ie <= 8" ] -
独立的 .browserslistrc 文件
# Browsers that we support > 1% last 2 versions not ie <= 8
以上配置表示:
-
全球使用率超过 1% 的浏览器
-
每个浏览器的最新两个版本
-
不包含 Internet Explorer 8 及更低版本
作用:
-
Babel: 会根据这个列表决定需要将哪些现代 JavaScript (ES6+) 语法转换为旧版 JavaScript,以确保能在这些目标浏览器中运行。例如,如果某个新语法在“目标浏览器”中有任何一个不支持,Babel 就会将其转换。
-
Autoprefixer: 会根据这个列表决定需要为哪些 CSS 属性添加厂商前缀(如 -webkit-, -moz-)。例如,如果某个 CSS 属性在目标浏览器中需要前缀才能工作,Autoprefixer 就会自动加上。
8.5、Shim 与 Polyfill
Shim(垫片)和 Polyfill(填充)不是一个具体的工具,是一种思想、策略。
核心目的都是在不改变外部使用方式的前提下,通过中间层适配,让旧环境支持新功能。
所有 Polyfill 都是 Shim,但不是所有 Shim 都是 Polyfill。
| 特性 | Polyfill | Shim |
|---|---|---|
| 范围 | 专用于填补标准 Web API的缺失 | 更广泛,可用于任何 API(标准或非标准) |
| 目的 | 让旧浏览器支持现代标准功能 | 修改、扩展或修复现有 API 行为 |
| 是否标准 | 必须遵循官方标准规范 | 不一定遵循标准 |
| 举例 | core-js | jQuery |
参考资料
前端常见浏览器兼容性问题
深入解析 core-js:现代 JavaScript 的兼容性基石
