Skip to content

浏览器兼容性踩坑

大白话解释: 浏览器兼容性问题就像"不同地方的插座不一样"。你在 Chrome 上写的代码,在 Safari 上可能就不工作了,尤其是在移动端 iOS Safari 上。

为什么会有兼容性问题?

  • 浏览器内核不同:Chrome 用 Blink,Safari 用 WebKit,实现方式有差异
  • 标准支持程度不同:新特性旧浏览器不支持
  • 浏览器的"特色行为":比如 iOS Safari 的 100vh 包含地址栏高度

重点关注哪些浏览器?

  • iOS Safari:移动端必须兼容,有很多"特色行为"
  • 旧版 Android WebView:低端安卓手机的浏览器
  • 微信内置浏览器:国内必须兼容

移动端和桌面端常见的浏览器兼容性问题,重点是 iOS Safari 和旧版 Android WebView。


预防措施

  1. 建立兼容性基线:项目开始前明确需要兼容的浏览器版本范围(如 iOS Safari 14+、Chrome 80+)
  2. 使用 Can I Use 查询:引入新 API 前先查 caniuse.com 确认兼容性
  3. 配置 Babel 和 PostCSS:构建工具自动处理语法转换和 CSS 前缀
  4. 真机测试:模拟器无法完全还原真机行为,关键功能必须真机验证
  5. 渐进增强:优先保证基础功能可用,再为现代浏览器添加增强体验

1. iOS Safari 100vh 不包含地址栏

css
/* 100vh 在 iOS Safari 中比实际可视区域高,底部元素会被遮挡 */
.fullscreen {
  height: 100vh;           /* ❌ iOS Safari 地址栏收起后超出 */
}

/* ✅ 使用 dvh(Dynamic Viewport Height,动态视口高度) */
/* dvh 会根据地址栏的显示/隐藏自动调整高度,解决 100vh 不准确的问题 */
.fullscreen {
  height: 100dvh;          /* Chrome 108+、Safari 16.4+,旧浏览器需降级方案 */
}

/* ✅ 兼容方案:CSS 变量 + JS 设置 */
:root {
  --vh: 1vh;              /* 定义 CSS 变量,初始值为 1vh */
}
.fullscreen {
  height: calc(var(--vh) * 100);  /* 用变量计算真实视口高度 */
}
js
// JS 计算真实视口高度
function setVH() {
  // 获取浏览器实际可视区域高度,乘以 0.01 得到 1% 的值
  document.documentElement.style.setProperty(
    '--vh', `${window.innerHeight * 0.01}px`  // 将计算结果设置到 CSS 变量 --vh
  )
}
// 监听窗口大小变化(如地址栏显示/隐藏、屏幕旋转)
window.addEventListener('resize', setVH)
setVH()  // 页面加载时立即执行一次

2. iOS Safari position: fixed 弹性滚动

css
/* iOS Safari 中 fixed 元素在滚动时会跟着页面抖动 */
.fixed-header {
  position: fixed;
  top: 0;
  /* ❌ 在 iOS Safari 中可能抖动 */
}
css
/* ✅ 使用 transform: translate3d 开启硬件加速 */
/* translate3d 是 CSS 3D 变换函数,第三个参数设为 0 可触发 GPU 加速, */
/* 让浏览器将该元素提升为独立的合成层,避免滚动时的抖动问题 */
.fixed-header {
  position: fixed;
  top: 0;
  transform: translate3d(0, 0, 0);  /* 开启硬件加速,解决 iOS Safari 抖动 */
}

/* ✅ 或使用 position: sticky 替代 */
.sticky-header {
  position: sticky;
  top: 0;
}

3. iOS 输入框被键盘遮挡

html
<!-- iOS Safari 中 input/textarea 聚焦时可能被键盘遮挡 -->
<input type="text">
js
// ✅ 监听 focusout/focusin 滚动到可视区域
const inputs = document.querySelectorAll('input, textarea')
inputs.forEach(input => {
  input.addEventListener('focus', () => {
    setTimeout(() => {
      input.scrollIntoView({ behavior: 'smooth', block: 'center' })
    }, 300)  // 等键盘弹出
  })
})
css
/* ✅ 或使用 CSS */
body {
  /* iOS 15+ 支持 */
  height: 100dvh;
}

4. iOS Safari 日期格式兼容

js
// ❌ iOS Safari 不支持 'YYYY-MM-DD HH:mm:ss' 格式
new Date('2024-01-01 12:00:00')  // Invalid Date

// ✅ 使用 '/' 分隔或 ISO 格式
new Date('2024/01/01 12:00:00')
new Date('2024-01-01T12:00:00')

// ✅ 或用 dayjs
import dayjs from 'dayjs'
dayjs('2024-01-01 12:00:00').toDate()

iOS Safari <input type="date"> 问题

html
<!-- iOS Safari 上 <input type="date"> 不支持 placeholder 属性 -->
<!-- 且日期格式固定为 YYYY-MM-DD,无法自定义显示格式 -->
<input type="date" placeholder="选择日期">  <!-- ❌ placeholder 不显示 -->
js
// ✅ 如需自定义格式或 placeholder,改用第三方日期选择器
// 推荐:vant 的 DatePicker、Element Plus 的 DatePicker

5. iOS Safari 下拉刷新冲突

css
/* iOS Safari 中 overscroll-behavior 不生效 */
/* 需要阻止默认行为来禁用下拉刷新 */
js
// ✅ 阻止 overscroll(需要在 touchmove 中阻止默认行为)
document.addEventListener('touchmove', (e) => {
  if (document.scrollingElement.scrollTop <= 0) {
    e.preventDefault()
  }
}, { passive: false })

6. Android WebView Flex 布局 bug

css
/* 旧版 Android WebView(< 5.0)不支持 Flex 布局 */
/* 或需要加 -webkit- 前缀 */
.container {
  display: -webkit-flex;   /* 兼容旧版 */
  display: flex;
  -webkit-flex-wrap: wrap; /* 兼容旧版 */
  flex-wrap: wrap;
}
css
/* 部分 Android 设备 flex-shrink: 0 不生效 */
.child {
  flex: 0 0 200px;
  min-width: 200px;        /* ✅ 加上 min-width 兼容 */
}

7. CSS Grid 兼容性

css
/* IE 11 只支持旧版 Grid 语法(-ms- 前缀) */
/* 不需要兼容 IE 时放心使用 */

/* 查看兼容性:https://caniuse.com/css-grid */

8. CSS 变量(custom properties)兼容

css
/* IE 11 不支持 CSS 变量 */
:root {
  --primary: #1890ff;
}
.btn {
  color: var(--primary);    /* ❌ IE 11 无效 */
  color: #1890ff;           /* ✅ 降级方案 */
}
css
/* ✅ PostCSS 插件自动降级 */
// postcss.config.js
module.exports = {
  plugins: [
    require('postcss-custom-properties')({ preserve: false })
  ]
}

9. async/await 兼容

js
// IE 11 不支持 async/await
// Vue CLI 项目 babel.config.js 中配置
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'  // 自动处理
  ]
}

// 手动 polyfill
import 'core-js/stable'
import 'regenerator-runtime/runtime'

10. fetch 兼容

js
// IE 11 不支持 fetch
// ✅ 使用 axios(自带 XMLHttpRequest 降级)
import axios from 'axios'

// ✅ 或 polyfill
import 'whatwg-fetch'

11. IntersectionObserver 兼容

js
// IE 11 不支持 IntersectionObserver
// ✅ polyfill
import 'intersection-observer'

// ✅ 或降级方案
function observeIntersection(el, callback) {
  if ('IntersectionObserver' in window) {
    const observer = new IntersectionObserver(callback)
    observer.observe(el)
  } else {
    // 降级:直接执行
    callback([{ isIntersecting: true }])
  }
}

12. passive 事件监听器

js
// Chrome 51+ 默认 touchstart/touchmove 为 passive
// 这意味着不能在监听器中 preventDefault()

// ❌ 以下代码在新版 Chrome 中无效
document.addEventListener('touchmove', (e) => {
  e.preventDefault()  // Chrome 会警告:Unable to preventDefault inside passive event listener
})

// ✅ 显式设置 passive: false
document.addEventListener('touchmove', (e) => {
  e.preventDefault()
}, { passive: false })

兼容性检测工具

工具用途
Can I Use查询 Web API 兼容性
BabelES6+ 语法转译
core-jsJS API polyfill
autoprefixerCSS 前缀自动补全
postcss-preset-env现代 CSS 降级

真实场景

场景 1:移动端底部悬浮按钮被遮挡

  • 问题:页面底部固定的"立即购买"按钮在 iOS Safari 上被地址栏遮挡,用户无法点击
  • 原因:使用了 height: 100vh,iOS Safari 的 100vh 包含了地址栏高度
  • 解决:改用 height: 100dvh 或通过 JS 动态计算视口高度

场景 2:iOS 上日期选择器显示"Invalid Date"

  • 问题:后端返回的 "2024-01-01 12:00:00" 格式在 iOS Safari 上解析失败
  • 原因:iOS Safari 的 Date 构造函数不支持空格分隔的日期格式
  • 解决:使用 ISO 格式 "2024-01-01T12:00:00" 或 dayjs 库解析

场景 3:固定导航栏滚动时抖动

  • 问题:iOS 上滚动页面时,position: fixed 的导航栏会跟着页面上下跳动
  • 原因:iOS Safari 的弹性滚动(橡皮筋效果)导致 fixed 定位计算异常
  • 解决:添加 transform: translate3d(0, 0, 0) 开启硬件加速

参考

个人学习笔记,部分内容借助 AI 辅助整理,仅供查阅参考,请以官方文档为准