移动端兼容问题概览
移动端兼容问题高度依赖机型、系统版本、内置 WebView 环境。同一套页面在 PC 正常,往往仅在特定手机或 App 内复现。
根本诱因:
- 新旧设备规则割裂:老旧机型内核停止更新,前缀、视口、
fixed定位沿用旧标准;新款机型引入刘海屏、安全区、动态工具栏,带来全新视口适配难题。 - WebView 环境不统一:系统 Safari、Chrome、第三方浏览器、微信等 App 内置浏览器,对滚动、触摸、软键盘、证书校验的实现逻辑各不相同。
- 网络环境更复杂:弱网、4G、Wi‑Fi 切换、代理环境普遍,请求超时、资源加载中断、TLS 握手失败概率远高于桌面端。
- 低端设备性能瓶颈:合成层滥用、大图、未节流的
scroll、touchmove监听,桌面无感的性能损耗,在手机上直接表现为卡顿。 - Web 标准落地进度不一致:
dvh、visualViewport、inputmode等新 CSS、Web API,各内核支持程度差异明显,不能直接以新版 Chrome 行为作为通用标准。
iOS 跟随滚动元素延迟、慢一拍
iOS 视觉滚动运行在 UI 线程,页面立刻滚动;而 scroll 事件在滚动位置更新后异步派发。页面已经滑动完成,监听 scroll 同步位置的元素才跟随移动,视觉上存在延迟。Android 滚动与 scroll 事件基本同步,不存在该现象。
优化方案:
- 滚动过程(手指滑动阶段):依靠
touchmove获取触摸坐标同步元素位置; - 惯性滚动(松手后):无
touchmove事件,只能依赖scroll,搭配requestAnimationFrame实时读取scrollTop。
注意:挂载在根节点的 touchmove 默认 passive: true;如果需要执行 preventDefault() 阻止滚动,必须显式传入 { passive: false },否则会产生冲突。
input disabled、readonly 在 iOS 常见坑
disabled:iOS 下输入框文字会变得极淡甚至看不见,Android 文字正常。仅展示、禁止编辑场景优先使用readonly。readonly:iOS 点击依旧唤起软键盘。不需要焦点时添加pointer-events: none;允许点击但禁止弹出键盘,使用inputmode="none",或在focus事件内执行blur()。
<input readonly inputmode="none" value="仅展示内容" />
移动端 click 是否还存在 300ms 延迟?
页面设置 width=device-width 时,现代 Chrome、Safari、Firefox 已经移除双击缩放带来的 300ms 延迟,新项目无需默认引入 FastClick。该延迟诞生初衷是区分单击与双击缩放;开启设备宽度视口后,浏览器不再等待判定时间。
若依旧感知点击发黏,给可交互元素添加样式,关闭双击缩放:
a,
button,
[role='button'] {
touch-action: manipulation;
}
触摸事件执行顺序保持不变:touchstart → touchmove → touchend → click。
注意:FastClick 在部分机型会造成 <input> 点击失效;Zepto tap 事件存在点击穿透问题,二者不建议作为通用方案。
iOS 软键盘弹出,fixed 元素错位
iOS 唤起软键盘时,只会修改可视视口,position: fixed 依旧基于布局视口计算,底部悬浮按钮、弹窗会上浮、遮挡或消失。多数 Android 不存在该问题;少数 WebView 收起键盘后页面无法自动复位。
无万能 CSS 方案,推荐适配策略:
- 输入区域避开
fixed布局,底部栏放入滚动容器,或改用absolute; - 使用 Visual Viewport API 监听视口变化,基于可视视口高度动态定位;
- 输入框失焦后执行
window.scrollTo(0, 0),修复 Android 键盘收起后的页面错位。
window.visualViewport.addEventListener('resize', () => {
document.documentElement.style.setProperty('--vvh',
`${window.visualViewport.height}px`,
)
})
备注:微信等 App WebView 软键盘行为由客户端控制,仅调整 H5 代码无法彻底解决所有场景。
Android 软键盘收起, fixed 弹窗错位
常见于微信内置 WebView:fixed 弹窗内部聚焦输入框,键盘顶起页面,点击空白区域收起键盘后,弹窗、遮罩无法恢复原始位置。
修复方案:输入框失焦强制复位页面滚动位置
input.addEventListener('blur', () => {
window.scrollTo(0, 0)
})
iOS html-to-image 导出图片空白
使用 html-to-image(同类库 dom-to-image)在 iOS 导出画布时,首次调用经常出现画布空白、内容残缺,二次调用恢复正常。根源是 Safari DOM 渲染写入 Canvas 的时序兼容问题。
封装建议:等待页面字体、图片资源全部加载完成后执行导出;iOS 环境增加二次调用兜底策略。
消除 transition 过渡闪屏
WebKit 内核中 3D 变换、CSS 过渡偶尔出现白屏闪烁、背面内容透出。针对闪烁节点添加三维属性,隐藏背面:
.layer {
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
注意:仅作用于出现闪屏的元素,不要全局添加。大量元素开启 3D 属性会生成过多合成层,造成动画卡顿。动画依旧优先使用 transform、opacity,避免 left、top。
拨打电话、短信、邮件唤起方式
使用系统协议链接,必须由用户主动点击 <a> 触发;直接通过脚本 location.href 跳转,大量 WebView 会进行拦截。
<a href="tel:0755-10086">打电话</a>
<a href="sms:10086">发短信</a>
<a href="mailto:peun@foxmail.com">写邮件</a>
短信携带正文存在系统差异:iOS 使用 &body=,Android 使用 ?body=,需要区分系统拼接链接;sms: 协议在 iOS、Android 主流环境可用。