跨平台 / 跨端 / 跨浏览器兼容

一、兼容性的三个层次

1
2
3
4
5
6
7
8
9
10
11
12
面试时常说的"兼容经验"涵盖三个层面:

跨浏览器兼容(Cross-browser)
Chrome / Firefox / Safari / Edge 对 CSS/JS/API 的支持差异

跨平台兼容(Cross-platform)
iOS(Safari WebView)vs Android(Chrome WebView)的行为差异
桌面端 vs 移动端的交互差异

跨端兼容(Cross-end)
H5 / 微信小程序 / React Native / Electron
相同业务逻辑在不同端上的代码复用与差异处理

二、跨浏览器兼容

2.1 浏览器内核差异

浏览器内核占比(中国)特征
ChromeBlink~65%标准支持最快,自动更新
SafariWebKit~25%封闭生态,iOS 强制使用,落后标准 1-2 个版本
FirefoxGecko~5%强隐私,DevTools 功能丰富
EdgeBlink~5%与 Chrome 一致,但有少量差异
微信内置浏览器X5(Blink 变体)大量基于 Chromium 但版本滞后,部分功能缺失

面试应答策略

1
2
3
4
5
兼容工作不是"所有浏览器 100% 效果一致"——这不现实。
而是:
1. 目标用户的主流浏览器覆盖(看数据,不看列表)
2. 核心功能可用(渐进增强,不影响使用)
3. 体验降级可接受(样式差异 ≠ 功能不可用)

2.2 CSS 兼容

自动补全(Autoprefixer)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 开发时 */
.card {
display: flex;
backdrop-filter: blur(10px);
}

/* Autoprefixer 处理后 */
.card {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
}

Vite 内置 Autoprefixer,Webpack 通过 postcss-loader + autoprefixer 引入。

常见 CSS 兼容场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌─ flexbox 兼容
│ 旧版 Safari(iOS < 9)需 -webkit-box
│ 绝大多数项目已不兼容 iOS 9 以下

├─ backdrop-filter
│ Chrome 可用,Safari 需 -webkit- 前缀
│ 不支持时降级为半透明背景色

├─ sticky 定位
│ 父容器 overflow: hidden 时会失效
│ iOS 11+ 稳定,可通过 position: -webkit-sticky 兼容

├─ aspect-ratio
│ 旧浏览器降级为 padding-bottom hack

├─ gap(flex 中使用)
│ Safari 15.4+ 才支持 flex gap
│ 降级为 margin + :not(:last-child)

├─ font-size 最小值
│ Chrome 桌面端最小 12px
│ Safari 可小至 6px(需注意)

└─ scroll-behavior: smooth
Safari 不支持
降级为 JS scrollIntoView

标准属性 vs 降级写法

1
2
3
4
5
6
/* 先写标准属性,再写降级(渐进增强) */
.card {
position: -webkit-sticky; /* Safari 降级 */
position: sticky;
top: 0;
}

2.3 JavaScript 兼容

语言特性兼容

1
2
3
4
5
6
┌─ ES6+ 语法 → Babel / esbuild 编译为 ES5
├─ Optional Chaining(?.)→ Babel @babel/plugin-optional-chaining
├─ Nullish Coalescing(??)→ 同上
├─ 箭头函数 → 自动转译为普通函数
├─ Promise → @babel/polyfill 引入 core-js
└─ Async/await → Babel runtime 支持

API 兼容

常用 API 的支持情况(2026 年参考):

APIChromeSafariFirefox降级方案
IntersectionObserver✅ 原生回退为 scroll 事件计算
ResizeObserver回退为 window.resize
WebSocket回退为轮询(极少需要)
CSS Typed OM使用 element.style
WebGPU✅ 实验性回退 WebGL
SharedArrayBuffer✅(跨域隔离)不可降级,需加响应头
import maps✅ 16.4+用打包工具替代

特性检测(Feature Detection)

1
2
3
4
5
6
7
8
9
10
// ✅ 正确做法:检测 API 是否存在
if ('IntersectionObserver' in window) {
new IntersectionObserver(callback).observe(el);
} else {
// 降级方案:监听 scroll 事件
window.addEventListener('scroll', fallbackHandler);
}

// ❌ 错误做法:检测用户代理(UA)
// if (/Chrome/.test(navigator.userAgent)) { ... }

2.4 跨浏览器测试策略

1
2
3
4
5
6
7
8
9
10
11
12
├── 开发期
│ ├── 用 Chrome 开发,Safari 定期验证
│ ├── 使用 BrowserStack / 真实设备做兼容测试
│ └── CSS:Autoprefixer + 特性查询 @supports

├── 自动化
│ ├── Playwright / Cypress 跑多浏览器 E2E
│ └── CI 中并行运行 Chrome + Firefox + WebKit

└── 线上
├── 错误监控(Sentry)按浏览器聚合
└── RUM 数据(Core Web Vitals)按浏览器对比

三、跨平台兼容

3.1 iOS vs Android 的 WebView 差异

差异点iOS(Safari WebView)Android(Chrome WebView)
内核WebKit(所有 iOS 浏览器强制使用)Blink / Chromium
版本更新随 iOS 系统更新,用户升级慢Play Store 独立更新,版本较新
内存限制严格(网页内存超限被杀掉)相对宽松
键盘弹起visualViewport 变化,window.innerHeight 缩小部分机型 resize 事件不触发
底部安全区域有(刘海屏指示条)有(各厂商不同)
Cookie 持久化严格(ITP 智能防追踪)正常
日期输入无原生 <input type="date">(需自行实现)原生支持
字体渲染较细,-webkit-font-smoothing: antialiased默认正常

3.2 iOS 常见兼容问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* 1. 滚动回弹效果 */
.container {
-webkit-overflow-scrolling: touch; /* iOS 弹性滚动 */
overflow-y: scroll;
}

/* 2. 取消 iPhone 按钮圆角 */
input, button {
-webkit-appearance: none;
border-radius: 0;
}

/* 3. 修复 100vh 问题(包含地址栏) */
.full-height {
height: -webkit-fill-available;
height: 100dvh; /* 动态视口高度 */
}

/* 4. 点击高亮去掉 */
* {
-webkit-tap-highlight-color: transparent;
}

/* 5. 修复圆角 + overflow: hidden 裁剪文字 */
.card {
border-radius: 12px;
overflow: hidden;
transform: translateZ(0); /* 触发 GPU 合成,修复裁剪 bug */
}

3.3 Android 常见兼容问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 1. 部分机型 1px 边框过粗 → 用伪元素缩放 */
.border-1px::after {
content: '';
position: absolute;
left: 0; bottom: 0;
width: 100%;
height: 1px;
background: #ddd;
transform: scaleY(0.5);
}

/* 2. WebView 暗黑模式强制反转颜色 */
html {
-webkit-text-size-adjust: 100%;
}

/* 3. 输入框缩放 */
input {
font-size: 16px; /* 防止 iOS 自动缩放 */
}

3.4 移动端 vs 桌面端交互差异

维度移动端桌面端
输入触摸(点击/滑动/长按)鼠标 + 键盘
悬停无 hoverhover 可用
屏幕尺寸小(320-430px)大(1024px+)
网络移动网络(弱网/离线)Wi-Fi/有线(稳定)
性能低功耗 CPU,内存有限高性能
存储IndexedDB/localStorage同上但容量更大
1
2
3
4
5
6
7
8
9
10
/* 区分触摸与鼠标 */
@media (hover: hover) and (pointer: fine) {
/* 桌面端:支持 hover */
.card:hover { transform: translateY(-4px); }
}

@media (hover: none) and (pointer: coarse) {
/* 移动端:触摸设备 */
.card:active { transform: scale(0.98); }
}

四、跨端兼容

4.1 多端架构模式

模式代码复用率性能团队成本代表
Web 优先 + 小程序60-70%一般同一套 Vue/React 代码,小程序部分重写
React Native / Weex80-90%较好同一 JS 逻辑,原生渲染
Flutter90%+最好高(需学 Dart)自渲染引擎,完全不依赖系统 WebView
小程序 + H5 同构70-80%一般Taro / uni-app 编译到多端
响应式 Web100%(仅 H5)依赖浏览器一套代码适配不同屏幕

4.2 Taro / uni-app 的兼容策略

1
2
3
4
5
6
7
8
9
10
11
12
// Taro 示例:一套代码编译到多端
function Index() {
return (
<View className="index">
<Text>Hello</Text>
</View>
);
}

// 编译为微信小程序:按 WXML 格式输出
// 编译为 H5:按 div + span 格式输出
// 编译为 RN:按 React Native 组件格式输出

跨端框架的兼容处理方式:

1
2
3
4
5
├── 组件映射:View → div / view / View(取决于目标端)
├── API 适配:Taro.request → wx.request / axios
├── 样式处理:CSS → WXSS / RN StyleSheet / 内联样式
├── 编译时处理:条件编译、按端裁剪
└── 运行时适配:抹平各端 API 差异

4.3 条件编译(uni-app)

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
// #ifdef MP-WEIXIN
console.log('仅微信小程序执行');
// #endif

// #ifdef H5
console.log('仅 H5 执行');
// #endif

// #ifndef MP-WEIXIN
console.log('非微信小程序执行');
// #endif
</script>

4.4 同一产品的 Web vs 小程序差异点

功能Web 实现小程序实现
页面路由React Router / Vue Router文件系统路由
网络请求fetch / axioswx.request
本地存储localStoragewx.setStorageSync
登录账号密码/OAuthwx.login + code2session
支付微信 H5 支付/支付宝wx.requestPayment
分享外部链接wx.shareAppMessage
更新刷新即最新wx.getUpdateManager

五、实践策略

5.1 渐进增强 vs 优雅降级

1
2
3
4
5
6
7
8
9
渐进增强(Progressive Enhancement):
从最低功能版本开始,逐步增加增强特性。
所有用户都可使用,高级浏览器获得更好的体验。

优雅降级(Graceful Degradation):
先构建完整功能,再为旧浏览器做降级。

推荐:渐进增强。
原因:移动端优先 + 低端设备优先,符合用户分布。

5.2 兼容性决策树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
遇到兼容问题:

├── 是 CSS 问题?
│ ├── 是否有 Autoprefixer 前缀 → 检查 postcss 配置
│ ├── 是否有标准替代属性 → 用 @supports 做降级
│ └── 样式差异是否影响功能 → 不影响则可接受

├── 是 JS API 问题?
│ ├── 是否有 Polyfill → 引用对应 polyfill
│ ├── 是否有替代 API → 用特性检测 + 降级
│ └── 是否存在 Edge case → 用 try-catch 兜底

├── 是 UI/交互差异?
│ ├── 不同端组件行为不一致 → 条件编译/按端适配
│ ├── 安全区域/刘海屏 → env(safe-area-inset-*) + 适配
│ └── 字体/渲染差异 → reset.css + 系统字体优化

└── 是性能差异?
├── 低端设备 → 减少动画/降低帧率
├── 弱网 → 离线缓存 + 骨架屏
└── 内存限制 → 分页加载 + 及时释放

5.3 常用的 Polyfill 与降级库

1
2
3
4
5
6
7
8
9
10
│ 特性                    │ Polyfill / 降级方案         │
│────────────────────────┼────────────────────────────│
│ IntersectionObserver │ 谷歌官方 polyfill │
│ ResizeObserver │ polyfill.io 按需加载 │
│ WebP │ <picture> 标签降级 │
│ CSS Nesting │ PostCSS 预处理 │
│ ES6+ 语法 │ Babel + core-js │
│ CSS Container Queries │ 用媒体查询 + JS 计算替代 │
│ backdrop-filter │ 降级为半透明背景色 │
│ WebSocket │ 降级为 HTTP 轮询 │

5.4 面试回答框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
当被问到"说一下你的兼容经验"时,按以下结构组织:

1. 明确范围
"我主要处理过三类兼容:
跨浏览器(Chrome/Safari/Firefox 的 CSS/API 差异)、
跨平台(iOS 与 Android 的 WebView 行为差异)、
跨端(同一产品同时有 H5 和微信小程序)。"

2. 具体案例(挑一个最典型的)
"例如在 XX 项目中,我们遇到了 iOS Safari 的 100vh 问题——
页面底部被地址栏遮挡。解决方案是使用 100dvh 代替 100vh,
不支持的浏览器回退为 -webkit-fill-available。"

3. 方法论
"我们建立了完善的兼容策略:
Autoprefixer 处理 CSS 前缀,
特性检测 + 降级处理 JS API,
BrowserStack 做多浏览器测试,
线上 Sentry 按浏览器监控错误率。"

4. 原则
"我的原则是:不追求所有浏览器 100% 一致,
而是保证核心功能可用 + 体验可接受 + 对目标用户覆盖达标。"

六、面试题

Q1: 如何处理 Safari 的 100vh 问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* Safari 中 100vh 包含地址栏高度,页面底部被遮挡 */
/* 方案一:dvh(动态视口高度,推荐) */
.full-height {
height: 100dvh;
}

/* 方案二:-webkit-fill-available(旧方案) */
.full-height {
height: -webkit-fill-available;
height: 100dvh;
}

/* 方案三:JS 动态计算 */
function setVh() {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}
window.addEventListener('resize', setVh);
setVh();
/* 使用:height: calc(var(--vh) * 100); */

Q2: 如何检测浏览器是否支持某个特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// CSS 特性检测
const supports = CSS.supports('backdrop-filter', 'blur(10px)');
CSS.supports('display', 'grid');

// JS API 检测
'IntersectionObserver' in window;
'ResizeObserver' in window;
'navigator.mediaDevices' in navigator;

// @supports 在 CSS 中做降级
@supports (backdrop-filter: blur(10px)) {
.modal { backdrop-filter: blur(10px); }
}
@supports not (backdrop-filter: blur(10px)) {
.modal { background: rgba(0, 0, 0, 0.5); }
}

Q3: 移动端 1px 边框怎么处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 方案一:伪元素 + scale(推荐) */
.hairline::after {
content: '';
position: absolute;
left: 0; bottom: 0;
width: 100%;
height: 1px;
background: #ddd;
transform: scaleY(0.5);
transform-origin: 0 100%;
}

@media (-webkit-min-device-pixel-ratio: 3) {
.hairline::after {
transform: scaleY(0.33);
}
}

/* 方案二:border-image + svg */
/* 方案三:直接设置 viewport scale */

Q4: 一套代码如何同时跑在 H5 和小程序上

1
2
3
4
5
6
主流方案:
├── Taro / uni-app / Remax
├── 编译时:将 React/Vue 代码编译为目标端代码
├── 运行时:提供统一的 API 适配层
├── 条件编译:各端差异化代码通过预编译指令隔离
└── 组件库:NutUI / NutUI 等支持多端的 UI 库

Q5: 什么是 Polyfill,什么情况下需要

1
2
3
4
5
6
7
8
9
10
11
12
13
Polyfill 是用 JS 模拟浏览器未实现的 API,让旧环境也能使用新特性。

需要的情况:
├── 目标用户使用旧浏览器(如国内大量微信 WebView)
├── 使用了较新的 ES API(Promise、Array.from)
├── 使用了新的 CSS 特性需要检测兼容

不需要的情况:
├── 仅在 Chrome 中使用的内部工具
├── Polyfill 体积过大、影响性能
└── 可以通过构建工具编译替代(如 Babel)

推荐:用 polyfill.io 按需加载,只加载当前浏览器缺少的。

Q6: 如何在项目中系统性地做兼容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
系统性兼容方案:

1. 确定目标环境
├── 从产品/运营获取用户浏览器分布数据
├── 确定兼容目标(如:Chrome 90+、Safari 15+、微信 8+)
└── 明确不支持的浏览器范围

2. 工具链配置
├── browserslist → 统一声明目标环境
├── Autoprefixer → 自动添加 CSS 前缀
├── Babel + core-js → 编译 JS 语法 + polyfill
└── Browserslist 与 Webpack/Vite/Babel 打通

3. 测试
├── 开发期:Chrome 为主,定期切 Safari 看效果
├── CI:Playwright 跑 Chrome + Firefox + WebKit
└── 线上:监控各浏览器错误率 + 性能指标

4. 文档
├── 记录已知兼容问题和解决方案
├── 新功能开发时标注兼容性
└── Review 时检查兼容处理