移动端适配
移动端适配概述
移动端适配是确保网页在不同设备上正常显示的关键,主要包括:
- 视口设置:viewport 配置
- 单位选择:rem、vw/vh、px
- 媒体查询:响应式设计
- 1px 问题:高分辨率屏幕处理
视口设置
viewport 配置
标准配置:
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
参数说明:
width=device-width:视口宽度等于设备宽度initial-scale=1.0:初始缩放比例maximum-scale=1.0:最大缩放比例user-scalable=no:禁止用户缩放
动态设置
function setViewport() {
const width = window.innerWidth || document.documentElement.clientWidth;
const scale = width / 375; // 设计稿宽度
const viewport = document.querySelector('meta[name="viewport"]');
viewport.setAttribute('content',
`width=375, initial-scale=${scale}, maximum-scale=${scale}, user-scalable=no`);
}
适配方案
1. rem 适配
原理:
- 根据屏幕宽度动态设置根元素字体大小
- 使用 rem 单位
实现:
(function() {
function setRem() {
const width = document.documentElement.clientWidth || document.body.clientWidth;
const rem = width / 10; // 375px 设计稿,1rem = 37.5px
document.documentElement.style.fontSize = rem + 'px';
}
setRem();
window.addEventListener('resize', setRem);
})();
使用:
/* 设计稿 375px,元素宽度 100px */
.element {
width: 2.67rem; /* 100 / 37.5 */
}
工具:
- postcss-pxtorem:自动转换 px 到 rem
2. vw/vh 适配
原理:
- 使用视口单位
- 1vw = 视口宽度的 1%
- 1vh = 视口高度的 1%
实现:
/* 设计稿 375px,元素宽度 100px */
.element {
width: 26.67vw; /* 100 / 375 * 100 */
}
工具:
- postcss-px-to-viewport:自动转换 px 到 vw
3. flexible.js(已废弃)
原理:
- 动态设置根元素字体大小
- 使用 rem 单位
- 处理 1px 问题
注意: 已不推荐使用,推荐使用 rem 或 vw/vh
4. 媒体查询
断点设置:
/* 移动端 */
@media screen and (max-width: 767px) {
.container {
width: 100%;
}
}
/* 平板 */
@media screen and (min-width: 768px) and (max-width: 1023px) {
.container {
width: 750px;
}
}
/* PC */
@media screen and (min-width: 1024px) {
.container {
width: 1200px;
}
}
1px 问题
问题原因
高分辨率屏幕:
- 设备像素比(DPR)> 1
- 1px CSS = 多个物理像素
- 导致边框变粗
解决方案
1. transform scale:
.border {
position: relative;
}
.border::after {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
background: #000;
transform: scaleY(0.5);
transform-origin: 0 0;
}
2. viewport 缩放:
const dpr = window.devicePixelRatio;
const scale = 1 / dpr;
const viewport = document.querySelector('meta[name="viewport"]');
viewport.setAttribute('content',
`width=device-width, initial-scale=${scale}, maximum-scale=${scale}`);
3. border-image:
.border {
border: 1px solid transparent;
border-image: linear-gradient(to bottom, #000 50%, transparent 50%) 1;
}
4. box-shadow:
.border {
box-shadow: 0 1px 0 0 #000;
}
移动端特殊处理
1. 禁止点击高亮
* {
-webkit-tap-highlight-color: transparent;
}
2. 禁止长按菜单
* {
-webkit-touch-callout: none;
user-select: none;
}
3. 禁止双击缩放
let lastTouchEnd = 0;
document.addEventListener('touchend', (e) => {
const now = Date.now();
if (now - lastTouchEnd <= 300) {
e.preventDefault();
}
lastTouchEnd = now;
}, false);
4. 禁止滚动穿透
function preventScroll(e) {
e.preventDefault();
}
// 打开弹窗时
document.body.style.overflow = 'hidden';
document.body.addEventListener('touchmove', preventScroll, { passive: false });
// 关闭弹窗时
document.body.style.overflow = '';
document.body.removeEventListener('touchmove', preventScroll);
5. 安全区域适配(iPhone X+)
.safe-area {
padding-bottom: constant(safe-area-inset-bottom); /* iOS 11.0-11.2 */
padding-bottom: env(safe-area-inset-bottom); /* iOS 11.2+ */
}
触摸事件
事件类型
触摸事件:
touchstart:触摸开始touchmove:触摸移动touchend:触摸结束touchcancel:触摸取消
事件对象:
element.addEventListener('touchstart', (e) => {
const touch = e.touches[0];
console.log(touch.clientX); // X 坐标
console.log(touch.clientY); // Y 坐标
});
手势识别
滑动:
let startX, startY;
element.addEventListener('touchstart', (e) => {
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
});
element.addEventListener('touchend', (e) => {
const endX = e.changedTouches[0].clientX;
const endY = e.changedTouches[0].clientY;
const deltaX = endX - startX;
const deltaY = endY - startY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 水平滑动
if (deltaX > 0) {
console.log('右滑');
} else {
console.log('左滑');
}
} else {
// 垂直滑动
if (deltaY > 0) {
console.log('下滑');
} else {
console.log('上滑');
}
}
});
性能优化
1. 减少重排重绘
/* 使用 transform 代替 top/left */
.element {
transform: translateX(100px);
}
2. 使用 will-change
.element {
will-change: transform;
}
3. 图片优化
- 使用 WebP 格式
- 懒加载
- 响应式图片
4. 减少 DOM 操作
- 使用虚拟滚动
- 批量操作
- 使用 DocumentFragment
调试技巧
1. Chrome DevTools
- 设备模拟器
- 网络节流
- 触摸事件模拟
2. 真机调试
Android:
- Chrome DevTools 远程调试
- USB 调试
iOS:
- Safari Web Inspector
- 需要 Mac
3. vConsole
<script src="https://cdn.jsdelivr.net/npm/vconsole/dist/vconsole.min.js"></script>
<script>
const vConsole = new VConsole();
</script>
最佳实践
1. 选择合适的适配方案
- rem:适合复杂布局
- vw/vh:适合简单布局
- 媒体查询:适合响应式设计
2. 处理 1px 问题
- 使用 transform scale
- 或使用 viewport 缩放
3. 优化触摸体验
- 合理设置触摸区域大小(44x44px)
- 添加触摸反馈
- 处理滚动穿透
4. 性能优化
- 减少重排重绘
- 图片优化
- 代码分割