前端知库

前端怎样做浏览器兼容?怎样确定要支持哪些浏览器?

先用自己网站的访问统计和业务要求定出支持范围,写成 browserslist 配置,让 Babel、Autoprefixer 等工具共用;开发时用 @supports 和 if ('x' in obj) 做特性检测,按渐进增强先保证基础功能可用,缺失的 API 按需加 polyfill;最后在范围内的最低版本和真机上测试,并定期更新浏览器数据。

参考答案

简答

产品经理说“要兼容主流浏览器”,这句话没法落地。实际做法是先看自己网站的访问统计,结合业务要求,比如是否有政企客户、海外用户占多少,定出一份具体的支持范围,再写成 browserslist 配置放进项目里。

这份配置是所有工具的共同依据。Babel、Autoprefixer、postcss-preset-env、eslint-plugin-compat 都会自动读取它,决定要转换哪些语法、补哪些 CSS 前缀、提示哪些 API 不支持。这样“支持哪些浏览器”只在一个地方定义,不会出现 JS 和 CSS 各按各的标准。

写代码时按渐进增强的思路:先保证所有支持范围内的浏览器都能完成核心操作,再对支持新特性的浏览器提供更好的体验。用 CSS 的 @supports 和 JS 的 if ('IntersectionObserver' in window) 做特性检测,缺失的 API 按需加 polyfill。

最后要实际测。在支持范围的最低版本和主要真机上跑一遍关键流程,并定期更新 browserslist 用到的浏览器数据,否则 last 2 versions 这类查询得到的版本会过时。

第一步:用数据定支持范围

支持范围应该来自自己用户的实际情况,而不是凭感觉。browserslist 的查询除了全球使用率,还支持 > 0.5% in my stats 这类按自定义统计数据计算的写法,README 里列出了从 Google Analytics、Plausible 等平台导出数据的工具。

如果没有统计数据,browserslist 提供了 defaults,等于 > 0.5%, last 2 versions, Firefox ESR, not dead,README 说它对大多数用户是合理的配置。not dead 用来排除超过 24 个月没有官方支持或更新的浏览器。想自己调整时,README 建议包含 last 2 versions, not dead, > 0.2%,原因是只写 last n versions 不会包含仍然流行的老版本,只按使用率筛选又会让流行的浏览器越来越流行。

README 还提醒了两点:不要因为不认识某个浏览器就把它删掉,比如 Opera Mini 在非洲有上亿用户;只有在给只有一种浏览器的设备做应用时,才直接写 last 2 Chrome versions 这样的指定版本。另外它支持按 Baseline 查询,比如 baseline widely available,选出能支持所有“广泛可用”特性的浏览器版本。

第二步:把范围写进配置

配置写在 package.json 的 browserslist 字段或项目根目录的 .browserslistrc 文件里:

text
> 0.5%
last 2 versions
not dead

在项目目录运行 npx browserslist,就能看到这份配置实际选出了哪些浏览器和版本。这一步很有用,因为 > 0.5% 这类条件选出来的结果并不直观,团队讨论时直接看列表更清楚。

各工具具体怎样根据这份列表工作,编译目标、polyfill 和 TypeScript 的 lib 分别管什么,见《编译目标、polyfill 和浏览器兼容性是什么关系?》;Babel 的转换原理见《Babel 是怎样把新语法转成旧语法的?语法转换和 polyfill 有什么区别?》。

第三步:写代码时做特性检测

特性检测是判断浏览器支不支持某个功能,而不是判断它是什么浏览器。为什么不判断浏览器名和版本?MDN 的说法是,真正需要知道的是某个功能能不能用,浏览器是哪个并不重要;同一个功能在不同浏览器里出现的版本又不一样,按功能本身判断更直接。

CSS 用 @supports。下面先写所有浏览器都能用的布局,支持 display: grid 的浏览器再改成网格:

CSS
.list {
  display: flex;
  flex-wrap: wrap;
}

@supports (display: grid) {
  .list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  }
}

JS 一般检查对象上有没有对应的属性或方法,MDN 的例子是 if ("geolocation" in navigator)。CSS 的支持情况也能在 JS 里用 CSS.supports() 判断。

JavaScript
if ('IntersectionObserver' in window) {
  // 用 IntersectionObserver 做懒加载
} else {
  // 直接加载全部图片
}

这就是渐进增强:不支持的浏览器仍能看到全部图片,只是少了懒加载带来的性能优化。MDN 对渐进增强的解释是,先给尽可能多的用户提供基本内容和功能,再给能运行新代码的浏览器提供更好的体验,通常用特性检测判断能力,用 polyfill 补上缺失的功能。

第四步:缺失的 API 用 polyfill 补

polyfill 是一段在运行时补上缺失 API 的代码,比如旧浏览器里没有 Array.prototype.at,polyfill 会在数组原型上定义这个方法。MDN 也提到,有些功能无法检测出来,这时候也要考虑 polyfill。

polyfill 只能补 JS 能实现的东西,比如方法和对象。新的语法要靠编译转换,有些 CSS 特性和浏览器底层能力也补不了,这些情况只能降级或换方案。polyfill 会增加体积,所以最好根据 browserslist 按需引入,而不是整包加载。

第五步:测试和维护

配置写对了不代表页面真的能用。要在支持范围内的最低版本上跑一遍登录、下单这类关键流程,移动端还要在真机上看,模拟器不能完全代表真实设备。eslint-plugin-compat 可以在写代码时提示用了列表里浏览器不支持的 API,把一部分问题提前到开发阶段。

browserslist 的版本数据来自 caniuse-lite,会随时间过时。README 推荐用 update-browserslist-db 更新数据,也有现成的 GitHub Action 定期自动更新并提交合并请求。支持范围本身也要定期回看统计数据调整。

常见追问

渐进增强和优雅降级有什么区别?

渐进增强是从基础版本做起,再给能力更强的浏览器加功能;优雅降级是先按新浏览器做完整版本,再想办法让旧浏览器不至于坏掉。两者最后的效果可能相同,区别在出发点。渐进增强能保证基础功能一开始就被考虑到,更不容易漏掉。

列表外的浏览器用户打开了页面怎么办?

至少不要白屏。可以检测几个关键能力,不满足时显示一个提示页,说明推荐使用的浏览器;或者保证核心内容能显示,交互功能降级。判断依据仍然是特性检测,而不是 User-Agent。

为什么 npx browserslist 输出的结果过一段时间会变?

因为 > 0.5%、last 2 versions 这些条件是按当前的浏览器数据计算的。浏览器发布新版本、使用率变化以后,同一份配置选出的版本就不一样了。这是期望的行为,配置描述的是规则,不是固定的版本号。