移动端适配
移动端适配
视口设置
<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 设置初始缩放比例为1.0
- maximum-scale=1.0 设置最大缩放比例为1.0
- user-scalable=no 设置用户不可手动缩放
主流适配方案
目标是让页面元素能够随屏幕的尺寸变化而等比例缩放
- REM
- 原理: REM是相对于根元素HTML字体大小的单位。通过JS动态设置根元素的 font-size,从而让所有使用REM为单位的元素按比例缩放 根字体大小 = 设备宽度 / 设计稿宽度 * 基准值 元素rem值 = 设计稿中的px值 / 基准值
- 步骤:
- 设置viewport
- 给设备宽度动态设置html的font-size
(function (doc, win) { const docEl = doc.documentElement; const resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize'; const recalc = function () { const clientWidth = docEl.clientWidth; if (!clientWidth) return; // 设计稿宽度375px,基准值37.5px const designWidth = 375; const baseSize = designWidth / 10; // 计算当前设备字体大小 const fontSize = (clientWidth / designWidth) * baseSize; // 限制范围,避免极端情况 fontSize = Math.max(fontSize, 12); fontSize = Math.min(fontSize, 64); docEl.style.fontSize = fontSize + 'px'; }; // 事件监听 win.addEventListener(resizeEvt, recalc, false); doc.addEventListener('DOMContentLoaded', recalc, false); recalc(); // 立即执行 })(document, window);
- 编写css 在 CSS 中,所有需要适配的尺寸(width, height, padding, margin, font-size 等)都使用 REM 单位。
- 换算技巧: 如果设计稿是 375px(iPhone 6/7/8 标准),则 1rem = 37.5px。一个 100px 的元素就是 100 / 37.5 ≈ 2.6667rem。
- 工具: 可以使用 VS Code 的 px to rem 插件、PostCSS 的 postcss-pxtorem 插件或 Sass/Less 函数来自动完成换算。
辅助工具
- JS转换函数
function px2rem(px, baseSize = 37.5) { return (px / baseSize).toFixed(4) + 'rem'; } // 使用示例 console.log(px2rem(100)); // "2.6667rem" console.log(px2rem(15)); // "0.4000rem"
- Sass/SCSS混合宏
@mixin px2rem($px, $baseSize: 37.5) { $rem: $px / $baseSize; @return #{$rem}rem; } .button { width: px2rem(100px); // 自动转换 height: px2rem(40px); font-size: px2rem(16px); }
- PostCSS自动转换
实际效果:// postcss-px-to-viewport配置 module.exports = { plugins: [ require('postcss-pxtorem')({ // 根字体大小 - 关键配置 rootValue: 37.5, // 375px设计稿 ÷ 10 // 精度控制 unitPrecision: 5, // 保留5位小数 // 属性白名单 - 哪些属性需要转换 propList: [ 'font', 'font-size', 'line-height', 'letter-spacing', 'width', 'height', 'margin*', 'padding*', 'border*' ], // 选择器黑名单 - 不转换的选择器 selectorBlackList: [ '.ignore', // 类名包含ignore的不转换 /^\.el-/, // Element UI的类名不转换 /^\.van-/ // Vant UI的类名不转换 ], // 替换模式 replace: true, // true: 替换px, false: 保留px并添加rem // 媒体查询转换 mediaQuery: false, // 不转换媒体查询中的px // 最小转换值 minPixelValue: 1, // 小于1px的不转换(如border: 0.5px) // 排除文件 exclude: /node_modules/i // 不转换node_modules中的文件 }) ] };
/* 实际效果 */ /* 输入的CSS */ .container { width: 375px; height: 200px; font-size: 16px; padding: 10px 15px; border: 1px solid #ccc; margin-top: 0.5px; /* 小于minPixelValue,不转换 */ } /* 输出的CSS */ .container { width: 10rem; /* 375px ÷ 37.5 = 10rem */ height: 5.33333rem; /* 200px ÷ 37.5 = 5.33333rem */ font-size: 0.42667rem; /* 16px ÷ 37.5 = 0.42667rem */ padding: 0.26667rem 0.4rem; /* 10px和15px转换 */ border: 0.02667rem solid #ccc; /* 1px转换 */ margin-top: 0.5px; /* 保持不变 */ }
- VW
- 原理: VW(Viewport Width)和 VH(Viewport Height)是直接相对于视口宽高的单位。
- 步骤:
- 设置viewport
- 直接使用vw编写css
- 换算:375px的设计稿,1vw = 3.75px。一个100px的元素就是100/3.75=26.6667vw
- 页面元素可以直接根据市口宽度等比缩放,无需js
- 媒体查询-响应式布局
- 原理: 通过查询设备的宽度(或高度、分辨率等),来应用不同的 CSS 样式块。通常与 REM 或百分比布局结合使用,用于在特定断点进行较大布局调整(如将两栏布局变成一栏布局)。
/* 为不同尺寸的屏幕设置不同的根字体大小 (配合REM方案) */
@media screen and (min-width: 375px) {
html {
font-size: 14px;
}
}
@media screen and (min-width: 414px) {
html {
font-size: 16px;
}
}
/* 或者直接改变布局结构 */
.container {
width: 100%;
display: flex;
flex-direction: column; /* 默认垂直排列 */
}
@media screen and (min-width: 768px) {
.container {
flex-direction: row; /* 屏幕宽度大于768px时改为水平排列 */
}
}
- 弹性布局
- 弹性盒布局 flex
- 栅格系统 grid
组合使用
- 设置viewport
- 核心缩放方案,REM+JS或者VW
- 布局辅助:Flex、Grid或者媒体查询
其他适配问题
1. 1px 边框变粗问题
在高清屏下,css的qpx可能对应多个物理像素,导致边框看起来较粗
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {
.retina-border {
position: relative;
border: none;
}
.retina-border::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid #ccc; /* 设置边框颜色 */
border-radius: 4px; /* 设置圆角 */
transform: scale(0.5);
transform-origin: 0 0;
pointer-events: none; /* 防止伪元素干扰点击 */
box-sizing: border-box;
}
}
2. 安全区适配
- HTML viewport 设置支持安全区域
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
.safe-area-padding{
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
/* 横屏时处理 */
padding-left: constant(safe-area-inset-left);
padding-left: env(safe-area-inset-left);
padding-right: constant(safe-area-inset-right);
padding-right: env(safe-area-inset-right);
}
3. 滚动条样式
- 默认滚动条样式不美观
/* 隐藏滚动条但保持滚动功能 */
.hide-scroll {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
}
.hide-scroll::-webkit-scrollbar {
display: none; /* Chrome/Safari */
}
/* Webkit内核浏览器 */
.custom-scroll::-webkit-scrollbar {
width: 6px; /* 竖直滚动条宽度 */
height: 6px; /* 水平滚动条高度 */
}
.custom-scroll::-webkit-scrollbar-track {
background: #f1f1f1; /* 轨道背景 */
border-radius: 3px;
}
.custom-scroll::-webkit-scrollbar-thumb {
background: #c1c1c1; /* 滑块颜色 */
border-radius: 3px; /* 滑块圆角 */
}
.custom-scroll::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
4. 点击300ms延迟
- 早期移动端浏览器为区分单机和双击缩放而存在200ms延迟
- 方案1 禁止缩放
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
html {
touch-action: manipulation; /* 阻止浏览器默认的双击缩放行为,间接消除延迟 */
}
- 方案2 如果需要兼容老浏览器 使用FastClick库
import FastClick from 'fastclick';
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
5. 滚动穿透
- 当弹出层上滚动时,背景页面也会随之滚动
function preventScrollThrough(open) {
const body = document.body;
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
if (open) {
body.style.overflow = 'hidden';
body.style.position = 'fixed';
body.style.top = `-${scrollTop}px`; // 记录并抵消当前滚动位置
body.style.width = '100%';
} else {
body.style.overflow = '';
body.style.position = '';
body.style.top = '';
body.style.width = '';
window.scrollTo(0, parseInt(scrollTop || '0')); // 恢复滚动位置
}
}
// 弹窗打开时调用
preventScrollThrough(true);
// 弹窗关闭时调用
preventScrollThrough(false);
6. iOS橡皮筋效果滚动
禁用iOS过度滚动的橡皮筋效果
/* 全局禁止 */
body, html {
width: 100%;
height: 100%;
overflow: hidden; /* 禁止全局滚动 */
}
/* 需要滚动的容器使用上面的 .scroll-container 样式。 */
.scroll-container {
height: 300px; /* 或 max-height */
overflow-y: auto;
-webkit-overflow-scrolling: touch; /* 启用iOS的顺滑滚动 */
}
7. 微信长按图片保存
防止微信中长按图片弹出保存菜单
img {
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
}
或者使用背景替代img标签,或者通过js阻止默认行为
document.addEventListener('contextmenu', function(e) {
if (e.target.nodeName === 'IMG') {
e.preventDefault(); // 阻止默认右键菜单(对长按菜单影响因浏览器而异)
}
});
8. 图片资源加载优化
- 懒加载
- 方案1
const lazyImages = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
});
lazyImages.forEach((img) => {
observer.observe(img);
});
- 方案2 使用loading=“lazy”
<img src="placeholder.jpg" data-src="real-image.jpg" alt="图片说明" loading="lazy">
- 响应式图片
- 方案1 使用picture元素
<!-- 不同屏幕尺寸加载不同图片 -->
<picture>
<source media="(min-width: 800px)" srcset="hero.jpg">
<source media="(min-width: 400px)" srcset="medium.jpg">
<img src="small.jpg" alt="后备图片">
</picture>
- 方案2 使用srcset和sizes属性
- srcset 属性允许我们为同一个
元素指定多个不同尺寸的图片源,让浏览器根据设备特性选择最合适的图片。
- w 单位表示图片的实际宽度(像素)
- sizes 属性告诉浏览器在不同断点下图片会显示的尺寸
- 浏览器会自动选择最适合当前设备的图片版本
<img
src="small.jpg"
srcset="small.jpg 300w,
medium.jpg 600w,
large.jpg 900w"
sizes="(max-width: 500px) 300px,
(max-width: 900px) 600px,
900px"
alt="响应式图片"
>
<img
src="image-1x.jpg"
srcset="image-1x.jpg 1x,
image-2x.jpg 2x,
image-3x.jpg 3x"
alt="不同像素密度的响应式图片">
9. 表单输入优化
- 正确使用input type,唤起更合适的虚拟键盘
<input type="tel" inputmode="numeric" pattern="[0-9]*"> <!-- 电话号码或数字 --> <input type="email" inputmode="email"> <!-- 邮箱 --> <input type="search"> <!-- 搜索框 --> <input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"> <!-- 禁用自动纠正和首字母大写 -->
10. 软键盘弹出问题
- 软键盘弹出时可能遮挡输入框
const input = document.querySelector('input');
input.addEventListener('focus', () => {
setTimeout(() => {
input.scrollIntoView({
behavior: 'smooth',
block: 'center' // 将输入框滚动到视口中心
});
}, 300);
});
作者
panxiao
发布日期
2025 - 09 - 02
许可证
Unlicensed
评论