在移动端开发中,我们经常会遇到各种奇怪的兼容性问题。今天就来总结一下那些常见的"坑"以及对应的解决方案。
1. 视口高度陷阱:100vh 不等于真实屏幕高度
陷阱现象
在移动端使用 100vh
设置全屏高度时,会发现页面底部被截断或出现滚动条。
问题原因
移动浏览器的地址栏和工具栏会动态显示/隐藏,但 100vh
包含了这些UI元素的高度,导致实际可视区域小于 100vh
。
解决方案
使用 100%
替代 100vh
js
.html,.body {
height: 100%;
}
2. 软键盘顶起底部导航的问题
陷阱现象
输入框聚焦后,软键盘弹出,底部固定的导航栏被顶起,影响用户体验。
问题原因
软键盘弹出时改变了视口高度,position: fixed
的元素相对于新的视口进行定位。
解决方案
使用 window.visualViewport
API 监听视口变化,动态调整布局。
js
// 监听视口变化
function handleViewportChange() {
const viewport = window.visualViewport;
if (!viewport) return;
const bottomBar = document.querySelector('.bottom-bar');
const isKeyboardOpen = viewport.height < window.innerHeight;
if (isKeyboardOpen) {
// 键盘打开时隐藏底部导航
bottomBar.style.display = 'none';
} else {
// 键盘关闭时显示底部导航
bottomBar.style.display = 'flex';
}
}
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', handleViewportChange);
}
3. 安全区域适配:刘海屏和虚拟按键
陷阱现象
在有刘海屏或虚拟按键的设备上,内容被遮挡或显示异常。
问题原因
没有考虑设备的安全区域(Safe Area),内容延伸到了状态栏或虚拟按键区域。
解决方案
使用 env()
和 constant()
函数获取安全区域数值,配合 @supports
做兼容性处理。
js
/* 安全区域适配 */
@supports (padding-bottom: env(safe-area-inset-bottom)) or (padding-bottom: constant(safe-area-inset-bottom)) {
.bottom-tabs {
padding-bottom: env(safe-area-inset-bottom);
height: calc(60px + env(safe-area-inset-bottom));
}
.placeholder {
height: calc(60px + env(safe-area-inset-bottom));
}
}
/* 顶部安全区域 */
.header {
padding-top: env(safe-area-inset-top);
background: linear-gradient(to bottom, #fff, #fff);
}
/* 左右安全区域 */
.container {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
4. 1px 边框问题
陷阱现象
在高分辨率屏幕上,1px 的边框看起来过于粗糙。
问题原因
设备像素比(DPR)导致 1px 实际显示为多个物理像素。
解决方案
使用伪元素 + transform 缩放实现真正的1px边框。
js
/* 1px边框解决方案 */
.border-1px {
position: relative;
}
.border-1px::after {
content: '';
position: absolute;
left: 0;
top: 0;
width: 200%;
height: 200%;
border: 1px solid #e6e6e6;
transform-origin: 0 0;
transform: scale(0.5);
box-sizing: border-box;
}
/* 针对不同DPR的适配 */
@media (-webkit-min-device-pixel-ratio: 2) {
.border-1px::after {
width: 200%;
height: 200%;
transform: scale(0.5);
}
}
@media (-webkit-min-device-pixel-ratio: 3) {
.border-1px::after {
width: 300%;
height: 300%;
transform: scale(0.33);
}
}
5. 点击延迟和穿透问题
陷阱现象
移动端点击有300ms延迟,或者点击上层元素后,下层元素也被触发。
问题原因
移动浏览器为了支持双击缩放,会延迟300ms判断是否为双击。点击穿透是因为touch事件和click事件的触发时序问题。
解决方案
使用 touch-action
CSS属性禁用延迟,或使用 FastClick 库。
js
/* 禁用点击延迟 */
.no-delay {
touch-action: manipulation;
}
/* 禁用所有手势 */
.no-touch {
touch-action: none;
}
// 防止点击穿透
function preventClickThrough(e) {
e.preventDefault();
e.stopPropagation();
// 延迟执行实际逻辑
setTimeout(() => {
// 执行点击逻辑
}, 300);
}
document.addEventListener('touchend', preventClickThrough);
6. iOS橡皮筋效果和滚动穿透
陷阱现象
iOS设备上滚动到边界时出现橡皮筋效果,或者弹窗滚动时背景页面也在滚动。
问题原因
iOS的默认滚动行为和事件冒泡机制导致。
解决方案
使用CSS和JS相结合的方式控制滚动行为。
js
/* 禁用橡皮筋效果 */
.no-bounce {
overscroll-behavior: none;
-webkit-overflow-scrolling: touch;
}
/* 锁定背景滚动 */
body.modal-open {
position: fixed;
width: 100%;
overflow: hidden;
}
// 弹窗打开时锁定背景滚动
function lockScroll() {
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
document.body.style.cssText += `
position: fixed;
width: 100%;
top: -${scrollTop}px;
`;
}
// 弹窗关闭时恢复滚动
function unlockScroll() {
const body = document.body;
const top = body.style.top;
body.style.position = '';
body.style.top = '';
window.scrollTo(0, Math.abs(parseFloat(top)));
}
7. 不可见探测器检测底部遮挡
陷阱现象
使用观察者api,当然了我们无法为每个DOM元素都添加遮挡检测,需要一个轻量级的全局检测方案来判断底部区域是否被设备UI遮挡。
问题原因
传统的适配方案需要为每个可能被遮挡的元素单独处理,代码复杂且性能开销大。
解决思路
在页面底部创建一个1px的不可见"探测器"元素,通过 Intersection Observer 检测它是否被遮挡,从而判断整个底部区域的遮挡情况,根据结果全局调整布局。
js
// 核心思想:创建底部探测器
function createBottomProbe() {
const probe = document.createElement('div');
probe.style.cssText = `
position: fixed; bottom: 0; left: 0;
width: 1px; height: 1px;
pointer-events: none; opacity: 0;
`;
document.body.appendChild(probe);
// 观察探测器是否被遮挡
new IntersectionObserver(([entry]) => {
const isObstructed = entry.intersectionRatio < 1;
document.documentElement.classList.toggle('bottom-obstructed', isObstructed);
}).observe(probe);
}
总结
移动端适配是一个恶心且复杂的工程,还有很多情况没有覆盖,需要考虑各种设备差异和浏览器兼容性。掌握这些常见问题的解决方案。记住,适配不是一蹴而就的,需要在实际项目中不断测试和得出方案。