移动端适配方案
移动端适配方案
1、媒体查询
通过媒体查询的方式,编写适应不同分辨率设备的的css样式
1 2 3 4 5 6 7 8 9 10 | @media screen and (max-width: 320px){ ....适配iphone4的css样式 } @media screen and (max-width: 375px){ ....适配iphone6/7/8的css样式 } @media screen and (max-width: 414px){ ....适配iphone6/7/8 plus的css样式 } ...... |
常用媒体类型:
all:所有设备
screen:用于电脑屏幕或其他使用屏幕显示设备的设备类型
speech:屏幕阅读器等发声设备
优点:
-
方法简单,只需修改css文件
-
调整屏幕宽度时不用刷新页面就可以响应页面布局
缺点:
-
代码量大,不方便维护
-
不能够完全适配所有的屏幕尺寸,需要编写多套css样式
2、动态 rem 方案(flexible + postcss-pxtorem)
lib-flexible的思想是根据不同屏幕修改根元素 font-size 大小,一般设置为屏幕宽度的十分之一
把整个视图宽度区域分为10份,每一份为一个1rem,总的就是10rem。
动态rem适配方案的流程:
- meta 标签设置 viewport 宽度为屏幕宽度;
1 | <meta name= "viewport" content= "width=device-width, initial-scale=1.0 user-scalable=no" ></meta> |
- 开发环境安装 lib-flexible,安装完成后在根目录index.js中引入
lib-flexible
:
- 开发环境安装配置 postcss-pxtorem 或者类似插件;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // 安装 npm install postcss-pxtorem -D // 配置参数 const path = require( "path" ) module.exports = ({ webpack }) => { // 某些组件库如vant视口宽度是375 const designWidth = webpack.resourcePath.includes(path.join( "node_modules" , "vant" )) ? 375 : 750 // 以设备宽度 750 为基准计算 vw 的值 return { plugins: { autoprefixer: {}, 'postcss-pxtorem' , { rootValue: 75, //设计图最大宽度除以10 //比如750的宽就写成75 selectorBlackList: [ 'test-' ], propList: [ '*' ], exclude: /node_modules/i } } } |
- 根据设计稿写样式,元素宽高直接取设计稿宽高即可,单位为 px,插件会将其转换为 rem;
- 段落文本也按照设计稿写,单位为px,不需要转换为 rem;
lib-flexible 其核心代码如下:
- 不同屏幕修改根元素 font-size 大小
1 2 3 4 5 6 7 | // set 1rem = viewWidth / 10 function setRemUnit () { var rem = docEl.clientWidth / 10 // docEl为document.documentElement,即html元素 docEl.style.fontSize = rem + 'px' } setRemUnit(); |
- 控制 viewport 的 width 和 scale 值适配高倍屏显示(规避了大家熟知的“1px问题)
1 2 3 4 | var metaEL= doc.querySelector( 'meta[name="viewport"]' ); var dpr = window.devicePixelRatio; var scale = 1 / dpr metaEl.setAttribute( 'content' , 'width=device-width, initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no' ); |
详细代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | ( function (win, lib) { var doc = win.document; var docEl = doc.documentElement; var metaEl = doc.querySelector( 'meta[name="viewport"]' ); var flexibleEl = doc.querySelector( 'meta[name="flexible"]' ); var dpr = 0; var scale = 0; var tid; var flexible = lib.flexible || (lib.flexible = {}); if (metaEl) { console.warn( '将根据已有的meta标签来设置缩放比例' ); var match = metaEl.getAttribute( 'content' ).match(/initial\-scale=([\d\.]+)/); if (match) { scale = parseFloat(match[1]); dpr = parseInt(1 / scale); } } else if (flexibleEl) { var content = flexibleEl.getAttribute( 'content' ); if (content) { var initialDpr = content.match(/initial\-dpr=([\d\.]+)/); var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/); if (initialDpr) { dpr = parseFloat(initialDpr[1]); scale = parseFloat((1 / dpr).toFixed(2)); } if (maximumDpr) { dpr = parseFloat(maximumDpr[1]); scale = parseFloat((1 / dpr).toFixed(2)); } } } if (!dpr && !scale) { var isAndroid = win.navigator.appVersion.match(/android/gi); var isIPhone = win.navigator.appVersion.match(/iphone/gi); var devicePixelRatio = win.devicePixelRatio; if (isIPhone) { // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案 if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) { dpr = 3; } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){ dpr = 2; } else { dpr = 1; } } else { // 其他设备下,仍旧使用1倍的方案 dpr = 1; } scale = 1 / dpr; } docEl.setAttribute( 'data-dpr' , dpr); if (!metaEl) { metaEl = doc.createElement( 'meta' ); metaEl.setAttribute( 'name' , 'viewport' ); metaEl.setAttribute( 'content' , 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no' ); if (docEl.firstElementChild) { docEl.firstElementChild.appendChild(metaEl); } else { var wrap = doc.createElement( 'div' ); wrap.appendChild(metaEl); doc.write(wrap.innerHTML); } } function refreshRem(){ var width = docEl.getBoundingClientRect().width; if (width / dpr > 540) { width = 540 * dpr; } var rem = width / 10; docEl.style.fontSize = rem + 'px' ; flexible.rem = win.rem = rem; } win.addEventListener( 'resize' , function () { clearTimeout(tid); tid = setTimeout(refreshRem, 300); }, false ); win.addEventListener( 'pageshow' , function (e) { if (e.persisted) { clearTimeout(tid); tid = setTimeout(refreshRem, 300); } }, false ); if (doc.readyState === 'complete' ) { doc.body.style.fontSize = 12 * dpr + 'px' ; } else { doc.addEventListener( 'DOMContentLoaded' , function (e) { doc.body.style.fontSize = 12 * dpr + 'px' ; }, false ); } refreshRem(); flexible.dpr = win.dpr = dpr; flexible.refreshRem = refreshRem; flexible.rem2px = function (d) { var val = parseFloat(d) * this .rem; if ( typeof d === 'string' && d.match(/rem$/)) { val += 'px' ; } return val; } flexible.px2rem = function (d) { var val = parseFloat(d) / this .rem; if ( typeof d === 'string' && d.match(/px$/)) { val += 'rem' ; } return val; } })(window, window[ 'lib' ] || (window[ 'lib' ] = {})); |
缺点:
- 对高倍屏的安卓手机没做处理(安卓设备下默认都设置为 dpr = 1)
- 不兼容响应式布局。
- 无法正确响应系统字体大小。
- flexible 对 iframe 的使用不兼容
3、Viewport 方案(postcss-px-to-viewport)
即将视觉视口宽度 window.innerWidth
和视觉视口高度 window.innerHeight
等分为 100 份。1vw 等于视口宽度的1%
vw 适配方案的流程:
- meta 标签设置 viewport 宽度为屏幕宽度;
1 | <meta name= "viewport" content= "width=device-width, initial-scale=1.0 user-scalable=no" ></meta> |
- 开发环境配置 postcss-px-to-viewport 或者类似插件;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // 安装 npm install postcss-px-to-viewport -D # or yarn add -D postcss-px-to-viewport # or pnpm add -D postcss-px-to-viewport // 配置参数 const path = require( "path" ) module.exports = ({ webpack }) => { // 某些组件库如vant视口宽度是375 const designWidth = webpack.resourcePath.includes(path.join( "node_modules" , "vant" )) ? 375 : 750 // 以设备宽度 750 为基准计算 vw 的值 return { plugins: { autoprefixer: {}, "postcss-px-to-viewport" : { unitToConvert: "px" , // 要转化的单位 viewportWidth: designWidth, unitPrecision: 6, // 转换后的精度,即小数点位数 propList: [ "*" ], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换 viewportUnit: "vw" , // 指定需要转换成的视窗单位,默认vw fontViewportUnit: "vw" , // 指定字体需要转换成的视窗单位,默认vw selectorBlackList: [], // 指定不转换为视窗单位的类名, minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换 mediaQuery: true , // 是否在媒体查询的css代码中也进行转换,默认false // replace: true, // 是否转换后直接更换属性值 exclude: [], // 设置忽略文件,用正则做目录名匹配 /node_modules/,使vant css转换 landscape: false // 是否处理横屏情况 } } } } |
- 根据设计稿写样式,元素宽高直接取设计稿宽高即可,单位为 px,插件会将其转换为 vw;
- 段落文本也按照设计稿写,单位为px,不需要转换为 vw;
优点:
- 实现简单:只需要通过安装插件和配置即可快速实现 px 转换为 vw 或 vh 单位。
- 提高开发效率:使用自适应布局,可以减少对不同屏幕尺寸的适配工作,提高开发效率。
-
适配性强:可以自适应不同设备和屏幕尺寸,适配性强,可以适应各种移动端设备的屏幕尺寸。
- 不需要动态计算根字体,页面渲染速度更快
缺点:
-
不适用于字体大小:由于 vw 和 vh 单位不适用于字体大小,因此需要单独设置字体大小的转换方式。
- 无法把行内样式中的 px 转换成视口单位(vw, vh, vmin, vmax),建议使用class,不适用行内样式
- 兼容性问题:一些老版本的浏览器不支持 vw 和 vh 单位,需要使用兼容性处理或回退方案
- 无法精确控制样式:由于浏览器的视口宽度和高度不同,转换后的样式可能会有一定的误差,无法精确控制样式
- 比如当容器使用
vw
,margin
采用px
时,很容易造成整体宽度超过100vw
,从而影响布局效果。当然我们也是可以避免的,例如使用padding
代替margin
,结合calc()
函数使用等等...
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)