移动端 H5 兼容问题合集:iOS 与 Android 的差异化处理
目标:系统梳理移动端 H5 的常见兼容问题,针对 iOS Safari/WKWebView 与 Android Chrome/WebView 的差异给出工程化解决方案与代码片段,可直接落地使用。
视口与安全区域
- meta 视口:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover">
- iOS 刘海屏安全区域:
.page {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
@supports (padding: constant(safe-area-inset-top)) {
.page {
padding-top: constant(safe-area-inset-top);
padding-bottom: constant(safe-area-inset-bottom);
padding-left: constant(safe-area-inset-left);
padding-right: constant(safe-area-inset-right);
}
}
100vh 与可视高度差异
- iOS Safari 的
100vh包含浏览器工具栏,导致视图溢出;使用dvh/svh/lvh与降级:
.full-height {
min-height: 100dvh;
}
@supports not (height: 100dvh) {
.full-height { min-height: 100vh; }
}
- JS 动态设置高度(旧端):
function setVH() {
const h = window.innerHeight
document.documentElement.style.setProperty('--vh', `${h * 0.01}px`)
}
setVH(); window.addEventListener('resize', setVH)
// 使用:height: calc(var(--vh) * 100)
输入框聚焦与键盘行为
- iOS 输入框字体过小会自动缩放,建议
font-size >= 16px:
input, textarea { font-size: 16px; }
- 键盘弹出遮挡底部固定栏:
function withKeyboardAware(footerEl) {
const onResize = () => {
const viewport = window.visualViewport
if (!viewport) return
const bottomInset = (window.innerHeight - viewport.height - viewport.offsetTop)
footerEl.style.transform = bottomInset > 0 ? `translateY(-${bottomInset}px)` : ''
}
window.visualViewport && window.visualViewport.addEventListener('resize', onResize)
}
- iOS 中
position: fixed在输入聚焦时抖动,可在键盘期禁用 fixed,改用 transform 过渡。
触摸、滚动与惯性
- iOS 惯性滚动:
.scroll {
overflow: auto;
-webkit-overflow-scrolling: touch;
}
- 橡皮筋回弹与穿透:
html, body { overscroll-behavior: none; }
.modal { overscroll-behavior: contain; }
- 阻止滚动卡顿,使用 passive 监听:
window.addEventListener('touchmove', handler, { passive: true })
- 手势响应:
.area { touch-action: pan-y; }
点击延迟与点击态
- 现代浏览器已移除 300ms 延迟;嵌套 WebView 或旧端若仍存在,推荐使用合规的
viewport或用pointer-events统一事件模型。 - 移动端点击态保持:
button:active { opacity: .7 }
视频音频播放差异
- iOS 禁止非静音自动播放:
<video src="x.mp4" muted playsinline webkit-playsinline autoplay></video>
- 需用户手势触发音频播放:
document.addEventListener('click', () => audio.play(), { once: true })
- WeChat iOS 内的内联播放需
playsinline与wx.ready后触发。
文件下载与打开
- iOS 对
a[download]支持有限,推荐 Blob 方案:
async function download(url, name) {
const res = await fetch(url)
const blob = await res.blob()
const a = document.createElement('a')
a.href = URL.createObjectURL(blob)
a.download = name
a.click()
URL.revokeObjectURL(a.href)
}
WebView 与内置浏览器差异
- Android WebView 多版本分裂,测试时关注
Chrome/WebView版本号。 - iOS WKWebView 与 Safari 行为不完全一致,尤其缓存与历史栈处理。
BFCache 与页面可见性
- iOS Safari 回退可能通过 BFCache 恢复页面:
window.addEventListener('pageshow', e => {
if (e.persisted) {
// 恢复状态与重新拉取必要数据
}
})
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
// 重新同步数据/刷新时钟
}
})
表单类型与选择器
- 不同浏览器对
input[type=date/time]支持不一致,业务上使用统一选择组件。 - 移除数字输入上下箭头(Android/部分端):
input[type=number] { -moz-appearance: textfield; }
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
CSS 兼容细节
- 1px 细线在高 DPR 设备的处理:
.hairline {
position: relative;
}
.hairline::after {
content: '';
position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
background: #e5e5e5; transform: scaleY(0.5); transform-origin: bottom;
}
- fixed 与 transform 叠加导致定位异常:避免将
position: fixed元素放在开启transform的父级内,或将 fixed 元素提升到根节点(Portal)。 position: sticky在overflow: hidden容器内表现不稳定,谨慎使用或改为监听滚动。
图片与懒加载
- IntersectionObserver 在旧端支持有限,使用降级:
function lazyLoad(imgs) {
if ('IntersectionObserver' in window) {
const io = new IntersectionObserver(entries => {
entries.forEach(e => { if (e.isIntersecting) { const el = e.target; el.src = el.dataset.src; io.unobserve(el) } })
})
imgs.forEach(img => io.observe(img))
} else {
const onScroll = () => {
imgs.forEach(img => {
const rect = img.getBoundingClientRect()
if (rect.top < window.innerHeight && rect.bottom > 0) img.src = img.dataset.src
})
}
window.addEventListener('scroll', onScroll, { passive: true })
onScroll()
}
}
性能与交互建议
- 避免在滚动中执行重计算,使用节流与 requestAnimationFrame。
- 资源加载使用
preload/prefetch与缓存策略,降低弱网抖动。 - 组件级别尽量无副作用,状态与样式分离,减少布局抖动。
检测与分支策略
- 能力检测优先于 UA 检测:
const support = {
dvh: CSS.supports('height', '100dvh'),
passive: false,
}
try { window.addEventListener('test', () => {}, Object.defineProperty({}, 'passive', { get() { support.passive = true } })) } catch {}
- 仅在必须时进行 UA 细分:
const ua = navigator.userAgent
const isIOS = /iP(hone|od|ad)/.test(ua)
const isAndroid = /Android/.test(ua)
常用问题清单与落地方案
- 顶部/底部被遮挡:
viewport-fit=cover+safe-areapadding - 页面高度异常:优先
100dvh,旧端用--vh动态变量 - 键盘遮挡:
visualViewport监听,移动底部栏或滚动到可视区 - 滚动穿透:
overscroll-behavior控制,必要时锁定背景滚动 - 自动播放失败:
muted + playsinline + 用户手势 - 下载不工作:Blob 方案替代
a[download] - 表单体验差:统一组件库,输入字号与占位优化
总结
- 兼容策略以“能力检测 + 渐进增强”为核心,尽量不做宽泛的 UA 分支。
- iOS 与 Android 的差异主要集中在视口、安全区域、媒体播放、滚动与键盘行为上。
- 建立可复用的工具与样式片段,形成团队级别的移动端兼容清单与模板。
1721

被折叠的 条评论
为什么被折叠?



